| | | 1 | | using Microsoft.Extensions.Caching.Memory; |
| | | 2 | | using Microsoft.Extensions.Http; |
| | | 3 | | using Microsoft.Extensions.Logging; |
| | | 4 | | using Microsoft.Extensions.Options; |
| | | 5 | | using OzonDomains.Models; |
| | | 6 | | using OzonRepositories.Data; |
| | | 7 | | using PartsInfo.Cache; |
| | | 8 | | using PartsInfo.HttpUtils; |
| | | 9 | | using Polly; |
| | | 10 | | using Polly.Extensions.Http; |
| | | 11 | | using Services.AiAssistant; |
| | | 12 | | using Services.ApiServices; |
| | | 13 | | using Services.ApiServices._1CApi; |
| | | 14 | | using Services.ApiServices._1CApi.OData; |
| | | 15 | | using Services.ApiServices._1CApi.Stock; |
| | | 16 | | using Services.ApiServices.ArmtekApi; |
| | | 17 | | using Services.ApiServices.ArmtekEtp; |
| | | 18 | | using Services.ApiServices.AvdApi; |
| | | 19 | | using OzonOrdersWeb.TransactionServices; |
| | | 20 | | using MikadoCartService = OzonOrdersWeb.TransactionServices.MikadoCartService; |
| | | 21 | | using Services.ApiServices.BergApi; |
| | | 22 | | using Services.ApiServices.ForumAutoApi; |
| | | 23 | | using Services.ApiServices.FidApi; |
| | | 24 | | using Services.ApiServices.InterpartsEngine; |
| | | 25 | | using Services.ApiServices.FavoritPartsApi; |
| | | 26 | | using Services.ApiServices.ApiSupplierRequestLog; |
| | | 27 | | using Services.ApiServices.ApiSupplierIntegration; |
| | | 28 | | using Services.LabelServices; |
| | | 29 | | using Services.OzonHandoverServices; |
| | | 30 | | using Services.ApiServices.MlAutoApi; |
| | | 31 | | using Services.ApiServices.ShateMApi; |
| | | 32 | | using Services.ApiServices.MotexApi; |
| | | 33 | | using Services.ApiServices.MoskvorechieApi; |
| | | 34 | | using Services.ApiServices.ProfitLigaApi; |
| | | 35 | | using Services.ApiServices.NikeiApi; |
| | | 36 | | using Services.ApiServices.MikadoApi; |
| | | 37 | | using Services.ApiServices.ZZapApi; |
| | | 38 | | using Services.ApiServices.JapartsApi; |
| | | 39 | | using Services.ApiServices.DropBoxApi; |
| | | 40 | | using Services.ApiServices.InterpartsPriceApi; |
| | | 41 | | using Services.ApiServices.OpenRouterApi; |
| | | 42 | | using Services.ApiServices.PartsnavigatorApi; |
| | | 43 | | using Services.ApiServices.OzonApi; |
| | | 44 | | using Services.ApiServices.StudioApi; |
| | | 45 | | using Services.ApiServices.TecDocApi; |
| | | 46 | | using Services.ApiServices.TelegramBotApi; |
| | | 47 | | using Services.ApiServices.YandexApi; |
| | | 48 | | using Services.BitrixStockServices; |
| | | 49 | | using Services.DataServices; |
| | | 50 | | |
| | | 51 | | namespace OzonOrdersWeb.Extensions; |
| | | 52 | | |
| | | 53 | | public static class ApiClientsExtensions |
| | | 54 | | { |
| | | 55 | | public static void AddApiClients(this IServiceCollection services, IConfiguration configuration) |
| | | 56 | | { |
| | 0 | 57 | | services.AddTransient<BitrixStockRunHttpLoggingHandler>(); |
| | 0 | 58 | | services.AddSingleton<ApiSupplierRequestLogStore>(); |
| | 0 | 59 | | services.AddHostedService<ApiSupplierRequestLogPersistenceService>(); |
| | 0 | 60 | | services.AddSingleton<InterpartsPriceDiagnosticsStore>(); |
| | | 61 | | // Настройка HttpClient с Polly политиками для всех API клиентов |
| | 0 | 62 | | services.AddHttpClient("OneCOData") |
| | 0 | 63 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | | 64 | | |
| | 0 | 65 | | services.AddHttpClient("OzonApi") |
| | 0 | 66 | | .AddHttpMessageHandler<BitrixStockRunHttpLoggingHandler>() |
| | 0 | 67 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()) |
| | 0 | 68 | | .AddPolicyHandler(BaseApiClient.GetCircuitBreakerPolicy()); |
| | | 69 | | |
| | | 70 | | // OzonApiReturns — только Retry, без Circuit Breaker (чтобы загрузка возвратов не блокировалась при открытом ко |
| | 0 | 71 | | services.AddHttpClient("OzonApiReturns") |
| | 0 | 72 | | .AddHttpMessageHandler<BitrixStockRunHttpLoggingHandler>() |
| | 0 | 73 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | | 74 | | |
| | | 75 | | // OzonApiBitrix — массовая загрузка остатков на BitrixStock, без Circuit Breaker |
| | 0 | 76 | | services.AddHttpClient("OzonApiBitrix") |
| | 0 | 77 | | .AddHttpMessageHandler<BitrixStockRunHttpLoggingHandler>() |
| | 0 | 78 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | | 79 | | |
| | | 80 | | // Скачивание готового файла этикетки по file_url (файловое хранилище Ozon, не api-seller). |
| | 0 | 81 | | services.AddHttpClient(OzonLabelPrintService.LabelDownloadHttpClientName, |
| | 0 | 82 | | client => client.Timeout = TimeSpan.FromSeconds(30)) |
| | 0 | 83 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | | 84 | | |
| | 0 | 85 | | services.AddHttpClient(OzonHandoverPhotoStorage.HttpClientName, |
| | 0 | 86 | | client => client.Timeout = TimeSpan.FromSeconds(30)) |
| | 0 | 87 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | | 88 | | |
| | 0 | 89 | | services.AddHttpClient("YandexApi") |
| | 0 | 90 | | .AddPolicyHandler(BaseApiClient.GetYandexRetryPolicy()) |
| | 0 | 91 | | .AddPolicyHandler(BaseApiClient.GetCircuitBreakerPolicy()); |
| | | 92 | | |
| | 0 | 93 | | services.AddHttpClient("TecDocApi") |
| | 0 | 94 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()) |
| | 0 | 95 | | .AddPolicyHandler(BaseApiClient.GetCircuitBreakerPolicy()); |
| | | 96 | | |
| | 0 | 97 | | services.AddHttpClient("InterpartsPriceApi") |
| | 0 | 98 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()) |
| | 0 | 99 | | .AddPolicyHandler(BaseApiClient.GetCircuitBreakerPolicy()); |
| | | 100 | | |
| | 0 | 101 | | services.AddTransient<OzonJsonDataBuilder>(serviceProvider => |
| | 0 | 102 | | { |
| | 0 | 103 | | var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>(); |
| | 0 | 104 | | var emailService = serviceProvider.GetService<SlqStudio.Application.Services.EmailService.IEmailService>(); |
| | 0 | 105 | | var clientId = configuration.GetConnectionString("ClientId"); |
| | 0 | 106 | | var apiKey = configuration.GetConnectionString("ApiKey"); |
| | 0 | 107 | | |
| | 0 | 108 | | var jsonDataBuilder = new OzonJsonDataBuilder(httpClientFactory, emailService); |
| | 0 | 109 | | if (clientId != null && apiKey != null) |
| | 0 | 110 | | jsonDataBuilder.Init(clientId, apiKey); |
| | 0 | 111 | | return jsonDataBuilder; |
| | 0 | 112 | | }); |
| | | 113 | | |
| | 0 | 114 | | services.AddTransient<YandexDataManager>(serviceProvider => |
| | 0 | 115 | | { |
| | 0 | 116 | | var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>(); |
| | 0 | 117 | | var emailService = serviceProvider.GetService<SlqStudio.Application.Services.EmailService.IEmailService>(); |
| | 0 | 118 | | var apiKey = configuration.GetConnectionString("YandexApiKey"); |
| | 0 | 119 | | |
| | 0 | 120 | | var yandexDataManager = new YandexDataManager(httpClientFactory, emailService); |
| | 0 | 121 | | if (apiKey != null) yandexDataManager.SetClient(apiKey); |
| | 0 | 122 | | return yandexDataManager; |
| | 0 | 123 | | }); |
| | | 124 | | |
| | 0 | 125 | | services.AddSingleton(new InterpartsPriceApiConfig |
| | 0 | 126 | | { |
| | 0 | 127 | | Secret123 = configuration.GetConnectionString("InterpartsPriceApiSecret123") ?? string.Empty, |
| | 0 | 128 | | BaseUrl = configuration["InterpartsPriceApi:BaseUrl"] ?? "http://price-api.interparts.ru/search.aspx" |
| | 0 | 129 | | }); |
| | | 130 | | |
| | 0 | 131 | | services.AddTransient<InterpartsPriceApiClient>(serviceProvider => |
| | 0 | 132 | | { |
| | 0 | 133 | | var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>(); |
| | 0 | 134 | | var emailService = serviceProvider.GetService<SlqStudio.Application.Services.EmailService.IEmailService>(); |
| | 0 | 135 | | return new InterpartsPriceApiClient(httpClientFactory, emailService); |
| | 0 | 136 | | }); |
| | | 137 | | |
| | 0 | 138 | | services.AddTransient<InterpartsPriceApiDataManager>(); |
| | 0 | 139 | | services.AddScoped<InterpartsCurrencyRateService>(); |
| | | 140 | | |
| | 0 | 141 | | services.AddSingleton(new DropboxApiClient( |
| | 0 | 142 | | configuration.GetConnectionString("DropBoxRefreshToken") ?? string.Empty, |
| | 0 | 143 | | configuration.GetConnectionString("DropBoxAppKey") ?? string.Empty, |
| | 0 | 144 | | configuration.GetConnectionString("DropBoxAppSecret") ?? string.Empty)); |
| | | 145 | | |
| | 0 | 146 | | services.AddTransient<TecDocApiClient>(serviceProvider => |
| | 0 | 147 | | { |
| | 0 | 148 | | var httpClientFactory = serviceProvider.GetRequiredService<IHttpClientFactory>(); |
| | 0 | 149 | | var emailService = serviceProvider.GetRequiredService<SlqStudio.Application.Services.EmailService.IEmailServ |
| | 0 | 150 | | var tecDocBaseUrl = configuration["TecDocApi:BaseUrl"] ?? "http://localhost:8000/"; |
| | 0 | 151 | | return new TecDocApiClient(httpClientFactory, emailService, tecDocBaseUrl); |
| | 0 | 152 | | }); |
| | 0 | 153 | | services.AddTransient<TecDocDataManager>(); |
| | | 154 | | |
| | | 155 | | |
| | | 156 | | // OData 1C Fresh интеграция |
| | 0 | 157 | | var odataConfig = new OneCODataConfig |
| | 0 | 158 | | { |
| | 0 | 159 | | BaseUrl = configuration["OneCOData:BaseUrl"] ?? string.Empty, |
| | 0 | 160 | | User = configuration["OneCOData:User"] ?? string.Empty, |
| | 0 | 161 | | Password = configuration["OneCOData:Password"] ?? string.Empty, |
| | 0 | 162 | | ResolverMode = Enum.TryParse<Services.ApiServices._1CApi.Nomenclature.NomenclatureResolverMode>( |
| | 0 | 163 | | configuration["OneCNomenclature:ResolverMode"], true, out var resolverMode) |
| | 0 | 164 | | ? resolverMode |
| | 0 | 165 | | : Services.ApiServices._1CApi.Nomenclature.NomenclatureResolverMode.On |
| | 0 | 166 | | }; |
| | 0 | 167 | | services.AddSingleton(odataConfig); |
| | | 168 | | |
| | 0 | 169 | | services.AddSingleton<OneCODataHttpClient>(serviceProvider => |
| | 0 | 170 | | { |
| | 0 | 171 | | var factory = serviceProvider.GetRequiredService<IHttpClientFactory>(); |
| | 0 | 172 | | var cfg = serviceProvider.GetRequiredService<OneCODataConfig>(); |
| | 0 | 173 | | return new OneCODataHttpClient(factory, cfg, |
| | 0 | 174 | | serviceProvider.GetService<Microsoft.Extensions.Logging.ILogger<OneCODataHttpClient>>()); |
| | 0 | 175 | | }); |
| | | 176 | | |
| | 0 | 177 | | services.AddSingleton<IOneCODataClient>(serviceProvider => serviceProvider.GetRequiredService<OneCODataHttpClien |
| | | 178 | | |
| | 0 | 179 | | services.AddSingleton<IOneCODataReferenceService>(serviceProvider => |
| | 0 | 180 | | { |
| | 0 | 181 | | var client = serviceProvider.GetRequiredService<OneCODataHttpClient>(); |
| | 0 | 182 | | var cache = serviceProvider.GetRequiredService<IMemoryCache>(); |
| | 0 | 183 | | var logger = serviceProvider.GetRequiredService<ILogger<OneCODataReferenceService>>(); |
| | 0 | 184 | | return new OneCODataReferenceService(client, cache, logger); |
| | 0 | 185 | | }); |
| | | 186 | | |
| | 0 | 187 | | services.AddSingleton<IOneCNomenclatureCreationLock, OneCNomenclatureCreationLock>(); |
| | 0 | 188 | | services.AddSingleton<IOneCNomenclatureMapStore, OneCNomenclatureMapStore>(); |
| | | 189 | | // Модуль поиска номенклатуры: снимок карты и справочник брендов держатся в памяти (Singleton), БД читают через |
| | 0 | 190 | | services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.INomenclatureSnapshotSource>(sp => |
| | 0 | 191 | | (Services.ApiServices._1CApi.Nomenclature.INomenclatureSnapshotSource)sp.GetRequiredService<IOneCNomenclatur |
| | 0 | 192 | | services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.IProducerDirectoryProvider>(sp => |
| | 0 | 193 | | new Services.ApiServices._1CApi.Nomenclature.ProducerRegistry(sp.GetRequiredService<IServiceScopeFactory>()) |
| | 0 | 194 | | services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.ProducerValueResolver>(sp => |
| | 0 | 195 | | new Services.ApiServices._1CApi.Nomenclature.ProducerValueResolver( |
| | 0 | 196 | | sp.GetRequiredService<IOneCODataClient>(), |
| | 0 | 197 | | sp.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.IProducerDirectoryProvider>())); |
| | 0 | 198 | | services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.NomenclatureMatcher>(sp => |
| | 0 | 199 | | new Services.ApiServices._1CApi.Nomenclature.NomenclatureMatcher( |
| | 0 | 200 | | sp.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.INomenclatureIndexProvider>(), |
| | 0 | 201 | | sp.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.IProducerDirectoryProvider>())); |
| | 0 | 202 | | services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.INomenclatureIndexProvider>(sp => |
| | 0 | 203 | | new Services.ApiServices._1CApi.Nomenclature.NomenclatureIndexProvider( |
| | 0 | 204 | | sp.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.INomenclatureSnapshotSource>(), |
| | 0 | 205 | | sp.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.IProducerDirectoryProvider>())); |
| | 0 | 206 | | services.AddSingleton<Services.ApiServices._1CApi.Nomenclature.INomenclatureNotificationSink, |
| | 0 | 207 | | OzonOrdersWeb.WebServices.SystemAlerts.NomenclatureTelegramNotifier>(); |
| | 0 | 208 | | services.AddSingleton<IOneCNomenclatureService>(serviceProvider => |
| | 0 | 209 | | { |
| | 0 | 210 | | var client = serviceProvider.GetRequiredService<OneCODataHttpClient>(); |
| | 0 | 211 | | var logger = serviceProvider.GetRequiredService<ILogger<OneCNomenclatureService>>(); |
| | 0 | 212 | | var creationLock = serviceProvider.GetRequiredService<IOneCNomenclatureCreationLock>(); |
| | 0 | 213 | | var map = serviceProvider.GetRequiredService<IOneCNomenclatureMapStore>(); |
| | 0 | 214 | | var cfg = serviceProvider.GetRequiredService<OneCODataConfig>(); |
| | 0 | 215 | | var signals = serviceProvider.GetRequiredService<Services.SystemMonitoringServices.IsolatedSystemMonitoringS |
| | 0 | 216 | | var matcher = serviceProvider.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.NomenclatureMatche |
| | 0 | 217 | | var producerValues = serviceProvider.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.ProducerVal |
| | 0 | 218 | | var indexProvider = serviceProvider.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.INomenclatur |
| | 0 | 219 | | var notifications = serviceProvider.GetRequiredService<Services.ApiServices._1CApi.Nomenclature.INomenclatur |
| | 0 | 220 | | return new OneCNomenclatureService(client, logger, creationLock, map, cfg, signals, matcher, producerValues, |
| | 0 | 221 | | }); |
| | 0 | 222 | | services.AddTransient<OneCNomenclatureMapSyncJob>(); |
| | | 223 | | |
| | 0 | 224 | | services.AddTransient<OneCTransferManager>(serviceProvider => |
| | 0 | 225 | | { |
| | 0 | 226 | | var refService = serviceProvider.GetRequiredService<IOneCODataReferenceService>(); |
| | 0 | 227 | | var odataClient = serviceProvider.GetRequiredService<OneCODataHttpClient>(); |
| | 0 | 228 | | var nomenclatureService = serviceProvider.GetRequiredService<IOneCNomenclatureService>(); |
| | 0 | 229 | | var warehouseMappingDataServices = serviceProvider.GetRequiredService<WarehouseMappingDataServices>(); |
| | 0 | 230 | | var ordersDataServices = serviceProvider.GetRequiredService<OrdersDataServices>(); |
| | 0 | 231 | | var logger = serviceProvider.GetRequiredService<ILogger<OneCTransferManager>>(); |
| | 0 | 232 | | var signals = serviceProvider.GetRequiredService<Services.SystemMonitoringServices.IsolatedSystemMonitoringS |
| | 0 | 233 | | var failureReporter = serviceProvider.GetRequiredService<Services.OneCServices.IOneCPostingFailureReporter>( |
| | 0 | 234 | | return new OneCTransferManager(refService, odataClient, nomenclatureService, warehouseMappingDataServices, o |
| | 0 | 235 | | }); |
| | | 236 | | |
| | 0 | 237 | | services.AddTransient<OneCReceiptManager>(serviceProvider => |
| | 0 | 238 | | { |
| | 0 | 239 | | var refService = serviceProvider.GetRequiredService<IOneCODataReferenceService>(); |
| | 0 | 240 | | var odataClient = serviceProvider.GetRequiredService<OneCODataHttpClient>(); |
| | 0 | 241 | | var nomenclatureService = serviceProvider.GetRequiredService<IOneCNomenclatureService>(); |
| | 0 | 242 | | var logger = serviceProvider.GetRequiredService<ILogger<OneCReceiptManager>>(); |
| | 0 | 243 | | var signals = serviceProvider.GetRequiredService<Services.SystemMonitoringServices.IsolatedSystemMonitoringS |
| | 0 | 244 | | var failureReporter = serviceProvider.GetRequiredService<Services.OneCServices.IOneCPostingFailureReporter>( |
| | 0 | 245 | | return new OneCReceiptManager(refService, odataClient, nomenclatureService, logger, signals, failureReporter |
| | 0 | 246 | | }); |
| | | 247 | | |
| | 0 | 248 | | services.AddTransient<Services.OneCServices.IOneCPostingFailureReporter, Services.OneCServices.OneCPostingFailur |
| | 0 | 249 | | services.AddTransient<OneCExpenseManager>(); |
| | | 250 | | |
| | 0 | 251 | | services.AddTransient<OneCDocumentViewService>(); |
| | | 252 | | |
| | 0 | 253 | | services.AddTransient<OneCDataManager>(serviceProvider => |
| | 0 | 254 | | { |
| | 0 | 255 | | var refService = serviceProvider.GetRequiredService<IOneCODataReferenceService>(); |
| | 0 | 256 | | var odataClient = serviceProvider.GetRequiredService<OneCODataHttpClient>(); |
| | 0 | 257 | | var logger = serviceProvider.GetRequiredService<ILogger<OneCDataManager>>(); |
| | 0 | 258 | | return new OneCDataManager(refService, odataClient, logger); |
| | 0 | 259 | | }); |
| | 0 | 260 | | services.AddTransient<OneCReceiptWarehouseHistoryResolver>(); |
| | | 261 | | |
| | 0 | 262 | | services.AddTransient<IOneCStockAvailabilityService, OneCStockAvailabilityService>(); |
| | | 263 | | |
| | | 264 | | // Раздел «Остатки 1С по складам». Singleton: сервис держит семафоры, которые не дают |
| | | 265 | | // параллельным открытиям одного склада запустить несколько тяжёлых обходов регистра. |
| | | 266 | | // Зависимости тоже Singleton, а к БД ходит через IServiceScopeFactory внутри map-store. |
| | 0 | 267 | | services.AddSingleton<IOneCWarehouseStockService, OneCWarehouseStockService>(); |
| | 0 | 268 | | services.AddTransient<IOneCWarehouseStockExcelExporter, OneCWarehouseStockExcelExporter>(); |
| | | 269 | | |
| | | 270 | | // Armtek Web-Services API — поставщик автозапчастей (read-only интеграция) |
| | 0 | 271 | | services.AddHttpClient("ArmtekApi") |
| | 0 | 272 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | | 273 | | |
| | 0 | 274 | | var armtekConfig = new ArmtekConfig |
| | 0 | 275 | | { |
| | 0 | 276 | | BaseUrl = configuration["Armtek:BaseUrl"] ?? "https://ws.armtek.ru", |
| | 0 | 277 | | User = configuration["Armtek:User"] ?? string.Empty, |
| | 0 | 278 | | Password = configuration["Armtek:Password"] ?? string.Empty, |
| | 0 | 279 | | DefaultVkorg = configuration["Armtek:DefaultVkorg"] ?? "4000" |
| | 0 | 280 | | }; |
| | 0 | 281 | | services.AddSingleton(armtekConfig); |
| | | 282 | | |
| | 0 | 283 | | services.AddSingleton<ArmtekHttpClient>(serviceProvider => |
| | 0 | 284 | | { |
| | 0 | 285 | | var factory = serviceProvider.GetRequiredService<IHttpClientFactory>(); |
| | 0 | 286 | | var cfg = serviceProvider.GetRequiredService<ArmtekConfig>(); |
| | 0 | 287 | | return new ArmtekHttpClient(factory, cfg, serviceProvider.GetRequiredService<ApiSupplierRequestLogStore>(), |
| | 0 | 288 | | dailyQuotaGuard: serviceProvider.GetRequiredService<ArmtekDailyQuotaGuard>()); |
| | 0 | 289 | | }); |
| | | 290 | | |
| | 0 | 291 | | services.AddTransient<ArmtekDataManager>(serviceProvider => |
| | 0 | 292 | | { |
| | 0 | 293 | | var client = serviceProvider.GetRequiredService<ArmtekHttpClient>(); |
| | 0 | 294 | | var cfg = serviceProvider.GetRequiredService<ArmtekConfig>(); |
| | 0 | 295 | | var logger = serviceProvider.GetRequiredService<ILogger<ArmtekDataManager>>(); |
| | 0 | 296 | | var resolver = new ArmtekBrandResolver( |
| | 0 | 297 | | client, |
| | 0 | 298 | | serviceProvider.GetRequiredService<EtProducerRepository>(), |
| | 0 | 299 | | serviceProvider.GetRequiredService<IMemoryCache>(), |
| | 0 | 300 | | "Armtek"); |
| | 0 | 301 | | return new ArmtekDataManager(client, cfg, logger, resolver); |
| | 0 | 302 | | }); |
| | | 303 | | |
| | 0 | 304 | | services.AddHttpClient("ArmtekByApi") |
| | 0 | 305 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 306 | | services.AddSingleton<ArmtekDailyQuotaGuard>(); |
| | 0 | 307 | | services.AddSingleton<ArmtekStoreDirectory>(); |
| | 0 | 308 | | services.AddHostedService<ArmtekStoreDirectoryHostedService>(); |
| | 0 | 309 | | services.AddSingleton(new ArmtekByConfig |
| | 0 | 310 | | { |
| | 0 | 311 | | BaseUrl = configuration["ArmtekBy:BaseUrl"] ?? "https://ws.armtek.ru", |
| | 0 | 312 | | User = configuration["ArmtekBy:User"] ?? string.Empty, |
| | 0 | 313 | | Password = configuration["ArmtekBy:Password"] ?? string.Empty, |
| | 0 | 314 | | DefaultVkorg = configuration["ArmtekBy:DefaultVkorg"] ?? "2000" |
| | 0 | 315 | | }); |
| | 0 | 316 | | services.AddSingleton<ArmtekByHttpClient>(); |
| | 0 | 317 | | services.AddTransient<ArmtekByDataManager>(serviceProvider => |
| | 0 | 318 | | { |
| | 0 | 319 | | var client = serviceProvider.GetRequiredService<ArmtekByHttpClient>(); |
| | 0 | 320 | | var cfg = serviceProvider.GetRequiredService<ArmtekByConfig>(); |
| | 0 | 321 | | var logger = serviceProvider.GetRequiredService<ILogger<ArmtekDataManager>>(); |
| | 0 | 322 | | var resolver = new ArmtekBrandResolver( |
| | 0 | 323 | | client, |
| | 0 | 324 | | serviceProvider.GetRequiredService<EtProducerRepository>(), |
| | 0 | 325 | | serviceProvider.GetRequiredService<IMemoryCache>(), |
| | 0 | 326 | | "ArmtekBy"); |
| | 0 | 327 | | return new ArmtekByDataManager(client, cfg, logger, resolver); |
| | 0 | 328 | | }); |
| | | 329 | | |
| | | 330 | | // Личный кабинет Armtek (etp.armtek.ru): взаиморасчёты и фактуры. Cookie сессии ведёт сам ArmtekEtpClient, |
| | | 331 | | // поэтому общий обработчик фабрики их не хранит и редиректы не проходит (редирект = страница входа). |
| | 0 | 332 | | var armtekEtpConfig = new ArmtekEtpConfig |
| | 0 | 333 | | { |
| | 0 | 334 | | Enabled = configuration.GetValue("ArmtekEtp:Enabled", false), |
| | 0 | 335 | | BaseUrl = configuration["ArmtekEtp:BaseUrl"] ?? "https://etp.armtek.ru", |
| | 0 | 336 | | Login = configuration["ArmtekEtp:Login"] is { Length: > 0 } etpLogin ? etpLogin : configuration["ArmtekBy:Us |
| | 0 | 337 | | Password = configuration["ArmtekEtp:Password"] is { Length: > 0 } etpPassword ? etpPassword : configuration[ |
| | 0 | 338 | | Vkorg = configuration["ArmtekEtp:Vkorg"] is { Length: > 0 } etpVkorg ? etpVkorg : configuration["ArmtekBy:De |
| | 0 | 339 | | Kunnr = configuration["ArmtekEtp:Kunnr"], |
| | 0 | 340 | | KunnrRg = configuration["ArmtekEtp:KunnrRg"], |
| | 0 | 341 | | LookbackDays = configuration.GetValue("ArmtekEtp:LookbackDays", 45), |
| | 0 | 342 | | InvoiceRefreshDays = configuration.GetValue("ArmtekEtp:InvoiceRefreshDays", 30), |
| | 0 | 343 | | OrderReportExtraDays = configuration.GetValue("ArmtekEtp:OrderReportExtraDays", 60), |
| | 0 | 344 | | InvoiceLoadLimit = configuration.GetValue("ArmtekEtp:InvoiceLoadLimit", 150), |
| | 0 | 345 | | InvoiceRequestDelayMs = configuration.GetValue("ArmtekEtp:InvoiceRequestDelayMs", 300) |
| | 0 | 346 | | }; |
| | 0 | 347 | | services.AddSingleton(armtekEtpConfig); |
| | 0 | 348 | | services.AddHttpClient(ArmtekEtpConfig.HttpClientName, c => c.Timeout = TimeSpan.FromSeconds(60)) |
| | 0 | 349 | | .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler |
| | 0 | 350 | | { |
| | 0 | 351 | | UseCookies = false, |
| | 0 | 352 | | AllowAutoRedirect = false, |
| | 0 | 353 | | AutomaticDecompression = System.Net.DecompressionMethods.All |
| | 0 | 354 | | }); |
| | 0 | 355 | | services.AddTransient<ArmtekEtpClient>(); |
| | | 356 | | |
| | 0 | 357 | | services.AddHttpClient("ShateMApi") |
| | 0 | 358 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 359 | | services.AddSingleton(new ShateMConfig |
| | 0 | 360 | | { |
| | 0 | 361 | | BaseUrl = configuration["ShateM:BaseUrl"] ?? "https://api.shate-m.by", |
| | 0 | 362 | | Login = configuration["ShateM:Login"] ?? string.Empty, |
| | 0 | 363 | | Password = configuration["ShateM:Password"] ?? string.Empty |
| | 0 | 364 | | }); |
| | 0 | 365 | | services.AddSingleton<ShateMHttpClient>(); |
| | 0 | 366 | | services.AddTransient<ShateMDataManager>(); |
| | 0 | 367 | | services.AddTransient<ShateMCartService>(); |
| | 0 | 368 | | services.AddTransient<IApiSupplierConnector, ArmtekSupplierConnector>(); |
| | 0 | 369 | | services.AddTransient<IApiSupplierConnector, ArmtekBySupplierConnector>(); |
| | 0 | 370 | | services.AddTransient<IApiSupplierConnector, ShateMSupplierConnector>(); |
| | 0 | 371 | | services.AddHttpClient("AvdApi") |
| | 0 | 372 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 373 | | services.AddSingleton(new AvdConfig |
| | 0 | 374 | | { |
| | 0 | 375 | | BaseUrl = configuration["Avd:BaseUrl"] ?? "https://ws1.avdmotors.ru/AvdUserService.svc/secure", |
| | 0 | 376 | | Login = configuration["Avd:Login"] ?? string.Empty, |
| | 0 | 377 | | Password = configuration["Avd:Password"] ?? string.Empty |
| | 0 | 378 | | }); |
| | 0 | 379 | | services.AddSingleton<AvdSoapClient>(); |
| | 0 | 380 | | services.AddTransient<AvdDataManager>(); |
| | 0 | 381 | | services.AddTransient<AvdBasketPlacementService>(); |
| | 0 | 382 | | services.AddTransient<ArmtekCartService>(); |
| | 0 | 383 | | services.AddScoped<ISupplierOrderGuard, SupplierOrderGuard>(); |
| | | 384 | | // Outbox намерений — singleton: переживает scope запроса и помнит, что таблицы ещё нет (до миграции). |
| | 0 | 385 | | services.AddSingleton<ISupplierOrderIntentStore, SupplierOrderIntentStore>(); |
| | 0 | 386 | | services.AddScoped<ISupplierOrderLookup, ArmtekOrderLookup>(); |
| | 0 | 387 | | services.AddScoped<ISupplierOrderLookup, FavoritPartsOrderLookup>(); |
| | 0 | 388 | | services.AddTransient<IApiSupplierConnector, AvdSupplierConnector>(); |
| | 0 | 389 | | services.AddHttpClient("JapartsApi", client => client.Timeout = TimeSpan.FromSeconds(30)) |
| | 0 | 390 | | .AddPolicyHandler(BaseApiClient.GetInteractiveRetryPolicy()); |
| | 0 | 391 | | services.AddSingleton(new JapartsConfig |
| | 0 | 392 | | { |
| | 0 | 393 | | BaseUrl = configuration["Japarts:BaseUrl"] ?? "https://www.japarts.ru/", |
| | 0 | 394 | | Login = configuration["Japarts:Login"] ?? string.Empty, |
| | 0 | 395 | | Password = configuration["Japarts:Password"] ?? string.Empty |
| | 0 | 396 | | }); |
| | 0 | 397 | | services.AddSingleton<JapartsHttpClient>(); |
| | 0 | 398 | | services.AddTransient<JapartsDataManager>(); |
| | 0 | 399 | | services.AddTransient<OzonOrdersWeb.TransactionServices.JapartsCartService>(); |
| | 0 | 400 | | services.AddTransient<IApiSupplierConnector, JapartsSupplierConnector>(); |
| | 0 | 401 | | services.AddHttpClient("BergApi") |
| | 0 | 402 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 403 | | services.AddSingleton(new BergConfig |
| | 0 | 404 | | { |
| | 0 | 405 | | BaseUrl = configuration["Berg:BaseUrl"] ?? "https://api.berg.ru", |
| | 0 | 406 | | ApiKey = configuration["Berg:ApiKey"] ?? string.Empty |
| | 0 | 407 | | }); |
| | 0 | 408 | | services.AddSingleton<BergHttpClient>(); |
| | 0 | 409 | | services.AddTransient<BergDataManager>(); |
| | 0 | 410 | | services.AddTransient<IApiSupplierConnector, BergSupplierConnector>(); |
| | 0 | 411 | | services.AddTransient<OzonOrdersWeb.TransactionServices.BergCartService>(); |
| | | 412 | | // Ответ фида ждёт открытая страница, поэтому таймаут короткий, а повтор — один быстрый. |
| | 0 | 413 | | services.AddHttpClient("FidApi", client => client.Timeout = TimeSpan.FromSeconds(20)) |
| | 0 | 414 | | .AddPolicyHandler(BaseApiClient.GetInteractiveRetryPolicy()); |
| | 0 | 415 | | services.AddSingleton(new FidApiConfig |
| | 0 | 416 | | { |
| | 0 | 417 | | BaseUrl = configuration["FidApi:BaseUrl"] ?? "http://192.168.1.130:5080", |
| | 0 | 418 | | ApiKey = configuration["FidApi:ApiKey"] ?? string.Empty |
| | 0 | 419 | | }); |
| | 0 | 420 | | services.AddSingleton<FidApiHttpClient>(); |
| | 0 | 421 | | services.AddTransient<FidApiDataManager>(); |
| | 0 | 422 | | services.AddHttpClient("MotexApi") |
| | 0 | 423 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 424 | | services.AddSingleton(new MotexConfig |
| | 0 | 425 | | { |
| | 0 | 426 | | BaseUrl = configuration["Motex:BaseUrl"] ?? "https://api.motexc.by", |
| | 0 | 427 | | Login = configuration["Motex:Login"] ?? string.Empty, |
| | 0 | 428 | | Password = configuration["Motex:Password"] ?? string.Empty |
| | 0 | 429 | | }); |
| | 0 | 430 | | services.AddSingleton<MotexHttpClient>(); |
| | 0 | 431 | | services.AddTransient<MotexDataManager>(); |
| | 0 | 432 | | services.AddTransient<MotexCartService>(); |
| | 0 | 433 | | services.AddTransient<IApiSupplierConnector, MotexSupplierConnector>(); |
| | 0 | 434 | | services.AddHttpClient("MlAutoApi") |
| | 0 | 435 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 436 | | services.AddSingleton(new MlAutoConfig |
| | 0 | 437 | | { |
| | 0 | 438 | | BaseUrl = configuration["MlAuto:BaseUrl"] ?? "https://www.ml-auto.by/webservice", |
| | 0 | 439 | | Login = configuration["MlAuto:Login"] ?? string.Empty, |
| | 0 | 440 | | Password = configuration["MlAuto:Password"] ?? string.Empty, |
| | 0 | 441 | | RequestsPerMinute = configuration.GetValue("MlAuto:RequestsPerMinute", 60) |
| | 0 | 442 | | }); |
| | 0 | 443 | | services.AddSingleton<MlAutoHttpClient>(); |
| | 0 | 444 | | services.AddTransient<MlAutoDataManager>(); |
| | 0 | 445 | | services.AddTransient<MlAutoCartService>(); |
| | 0 | 446 | | services.AddTransient<IApiSupplierConnector, MlAutoSupplierConnector>(); |
| | 0 | 447 | | services.AddHttpClient("MlAutoRuApi") |
| | 0 | 448 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 449 | | services.AddSingleton(new MlAutoRuConfig |
| | 0 | 450 | | { |
| | 0 | 451 | | BaseUrl = configuration["MlAutoRu:BaseUrl"] ?? "https://ml-auto.ru/webservice", |
| | 0 | 452 | | Login = configuration["MlAutoRu:Login"] ?? string.Empty, |
| | 0 | 453 | | Password = configuration["MlAutoRu:Password"] ?? string.Empty, |
| | 0 | 454 | | RequestsPerMinute = configuration.GetValue("MlAutoRu:RequestsPerMinute", 60) |
| | 0 | 455 | | }); |
| | 0 | 456 | | services.AddSingleton<MlAutoRuHttpClient>(); |
| | 0 | 457 | | services.AddTransient<MlAutoRuDataManager>(); |
| | 0 | 458 | | services.AddTransient<MlAutoRuCartService>(); |
| | 0 | 459 | | services.AddTransient<IApiSupplierConnector, MlAutoRuSupplierConnector>(); |
| | 0 | 460 | | services.AddHttpClient("ForumAutoApi") |
| | 0 | 461 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 462 | | services.AddSingleton(new ForumAutoConfig |
| | 0 | 463 | | { |
| | 0 | 464 | | BaseUrl = configuration["ForumAuto:BaseUrl"] ?? "https://api.forum-auto.ru/v2", |
| | 0 | 465 | | Login = configuration["ForumAuto:Login"] ?? string.Empty, |
| | 0 | 466 | | Password = configuration["ForumAuto:Password"] ?? string.Empty |
| | 0 | 467 | | }); |
| | 0 | 468 | | services.AddSingleton<ForumAutoHttpClient>(); |
| | 0 | 469 | | services.AddTransient<ForumAutoDataManager>(); |
| | 0 | 470 | | services.AddTransient<IApiSupplierConnector, ForumAutoSupplierConnector>(); |
| | 0 | 471 | | services.AddHttpClient("ForumAutoInterpartsApi") |
| | 0 | 472 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 473 | | services.AddSingleton(new ForumAutoInterpartsConfig |
| | 0 | 474 | | { |
| | 0 | 475 | | BaseUrl = configuration["ForumAutoInterparts:BaseUrl"] ?? "https://api.forum-auto.ru/v2", |
| | 0 | 476 | | Login = configuration["ForumAutoInterparts:Login"] ?? string.Empty, |
| | 0 | 477 | | Password = configuration["ForumAutoInterparts:Password"] ?? string.Empty |
| | 0 | 478 | | }); |
| | 0 | 479 | | services.AddSingleton<ForumAutoInterpartsHttpClient>(); |
| | 0 | 480 | | services.AddTransient<ForumAutoInterpartsDataManager>(); |
| | 0 | 481 | | services.AddTransient<IApiSupplierConnector, ForumAutoInterpartsSupplierConnector>(); |
| | 0 | 482 | | services.AddHttpClient("ForumAutoPiterApi") |
| | 0 | 483 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 484 | | services.AddSingleton(new ForumAutoPiterConfig |
| | 0 | 485 | | { |
| | 0 | 486 | | BaseUrl = configuration["ForumAutoPiter:BaseUrl"] ?? "https://api.forum-auto.ru/v2", |
| | 0 | 487 | | Login = configuration["ForumAutoPiter:Login"] ?? string.Empty, |
| | 0 | 488 | | Password = configuration["ForumAutoPiter:Password"] ?? string.Empty |
| | 0 | 489 | | }); |
| | 0 | 490 | | services.AddSingleton<ForumAutoPiterHttpClient>(); |
| | 0 | 491 | | services.AddTransient<ForumAutoPiterDataManager>(); |
| | 0 | 492 | | services.AddTransient<IApiSupplierConnector, ForumAutoPiterSupplierConnector>(); |
| | 0 | 493 | | services.AddHttpClient("ForumAutoRostovApi") |
| | 0 | 494 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 495 | | services.AddSingleton(new ForumAutoRostovConfig |
| | 0 | 496 | | { |
| | 0 | 497 | | BaseUrl = configuration["ForumAutoRostov:BaseUrl"] ?? "https://api.forum-auto.ru/v2", |
| | 0 | 498 | | Login = configuration["ForumAutoRostov:Login"] ?? string.Empty, |
| | 0 | 499 | | Password = configuration["ForumAutoRostov:Password"] ?? string.Empty |
| | 0 | 500 | | }); |
| | 0 | 501 | | services.AddSingleton<ForumAutoRostovHttpClient>(); |
| | 0 | 502 | | services.AddTransient<ForumAutoRostovDataManager>(); |
| | 0 | 503 | | services.AddTransient<IApiSupplierConnector, ForumAutoRostovSupplierConnector>(); |
| | 0 | 504 | | services.AddHttpClient("ForumAutoIstraApi") |
| | 0 | 505 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 506 | | services.AddSingleton(new ForumAutoIstraConfig |
| | 0 | 507 | | { |
| | 0 | 508 | | BaseUrl = configuration["ForumAutoIstra:BaseUrl"] ?? "https://api.forum-auto.ru/v2", |
| | 0 | 509 | | Login = configuration["ForumAutoIstra:Login"] ?? string.Empty, |
| | 0 | 510 | | Password = configuration["ForumAutoIstra:Password"] ?? string.Empty |
| | 0 | 511 | | }); |
| | 0 | 512 | | services.AddSingleton<ForumAutoIstraHttpClient>(); |
| | 0 | 513 | | services.AddTransient<ForumAutoIstraDataManager>(); |
| | 0 | 514 | | services.AddTransient<IApiSupplierConnector, ForumAutoIstraSupplierConnector>(); |
| | 0 | 515 | | services.AddTransient<ForumAutoCartService>(); |
| | | 516 | | // Таймаут задан явно: со значением HttpClient по умолчанию (100 с) три повтора политики |
| | | 517 | | // растягивают один вызов почти на 7 минут и держат поток запроса пользователя. |
| | 0 | 518 | | services.AddHttpClient("FavoritPartsPriceApi", client => client.Timeout = TimeSpan.FromSeconds(30)) |
| | 0 | 519 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | | 520 | | // Повторы только для GET: POST ws/v1/order/ не идемпотентен — на таймауте или 500 заказ у |
| | | 521 | | // поставщика мог уже создаться, и повтор превратился бы в дубль заказа. |
| | 0 | 522 | | var favoritPartsReadRetry = BaseApiClient.GetRetryPolicy(); |
| | 0 | 523 | | var favoritPartsNoRetry = Policy.NoOpAsync<HttpResponseMessage>(); |
| | 0 | 524 | | services.AddHttpClient("FavoritPartsOrderApi", client => client.Timeout = TimeSpan.FromSeconds(45)) |
| | 0 | 525 | | .AddPolicyHandler(request => request.Method == HttpMethod.Get ? favoritPartsReadRetry : favoritPartsNoRetry) |
| | | 526 | | // Через Configure, а не singleton-снимок: ротация ключа покупателя/разработчика в конфигурации |
| | | 527 | | // подхватывается по IOptionsMonitor без перезапуска приложения. |
| | 0 | 528 | | services.Configure<FavoritPartsConfig>(configuration.GetSection(FavoritPartsConfig.SectionName)); |
| | 0 | 529 | | services.Configure<FavoritPartsConfig>(FavoritPartsConfig.IstraSectionName, configuration.GetSection(FavoritPart |
| | 0 | 530 | | services.Configure<FavoritPartsConfig>(FavoritPartsConfig.RostovSectionName, configuration.GetSection(FavoritPar |
| | 0 | 531 | | services.PostConfigure<FavoritPartsConfig>(config => |
| | 0 | 532 | | { |
| | 0 | 533 | | if (string.IsNullOrWhiteSpace(config.BaseUrl)) config.BaseUrl = "https://api.favorit-parts.ru"; |
| | 0 | 534 | | }); |
| | 0 | 535 | | services.AddSingleton<FavoritPartsPriceHttpClient>(); |
| | 0 | 536 | | services.AddSingleton<FavoritPartsOrderHttpClient>(); |
| | 0 | 537 | | services.AddTransient<FavoritPartsDataManager>(); |
| | 0 | 538 | | services.AddTransient<IApiSupplierConnector, FavoritPartsSupplierConnector>(); |
| | 0 | 539 | | services.AddTransient<FavoritPartsCartService>(); |
| | 0 | 540 | | services.AddTransient<FavoritPartsIstraDataManager>(sp => new FavoritPartsIstraDataManager( |
| | 0 | 541 | | new FavoritPartsPriceHttpClient(sp.GetRequiredService<IHttpClientFactory>(), sp.GetRequiredService<IOptionsM |
| | 0 | 542 | | new FavoritPartsOrderHttpClient(sp.GetRequiredService<IHttpClientFactory>(), sp.GetRequiredService<IOptionsM |
| | 0 | 543 | | services.AddTransient<IApiSupplierConnector, FavoritPartsIstraSupplierConnector>(); |
| | 0 | 544 | | services.AddTransient<FavoritPartsIstraCartService>(); |
| | 0 | 545 | | services.AddTransient<FavoritPartsRostovDataManager>(sp => new FavoritPartsRostovDataManager( |
| | 0 | 546 | | new FavoritPartsPriceHttpClient(sp.GetRequiredService<IHttpClientFactory>(), sp.GetRequiredService<IOptionsM |
| | 0 | 547 | | new FavoritPartsOrderHttpClient(sp.GetRequiredService<IHttpClientFactory>(), sp.GetRequiredService<IOptionsM |
| | 0 | 548 | | services.AddTransient<IApiSupplierConnector, FavoritPartsRostovSupplierConnector>(); |
| | 0 | 549 | | services.AddTransient<FavoritPartsRostovCartService>(); |
| | 0 | 550 | | services.AddHttpClient("MoskvorechieIstraApi", client => client.Timeout = TimeSpan.FromSeconds(30)) |
| | 0 | 551 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 552 | | services.Configure<MoskvorechieIstraConfig>(configuration.GetSection(MoskvorechieIstraConfig.SectionName)); |
| | 0 | 553 | | services.PostConfigure<MoskvorechieIstraConfig>(config => |
| | 0 | 554 | | { |
| | 0 | 555 | | if (string.IsNullOrWhiteSpace(config.BaseUrl)) config.BaseUrl = "https://portal.moskvorechie.ru/portal.api"; |
| | 0 | 556 | | }); |
| | 0 | 557 | | services.AddSingleton<MoskvorechieIstraHttpClient>(); |
| | 0 | 558 | | services.AddSingleton<MoskvorechiePortalPriceClient>(); |
| | 0 | 559 | | services.AddTransient<MoskvorechieIstraDataManager>(); |
| | 0 | 560 | | services.AddTransient<IApiSupplierConnector, MoskvorechieIstraSupplierConnector>(); |
| | 0 | 561 | | services.AddTransient<MoskvorechieIstraCartService>(); |
| | 0 | 562 | | services.AddHttpClient("ProfitLigaApi") |
| | 0 | 563 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 564 | | services.AddSingleton(new ProfitLigaConfig |
| | 0 | 565 | | { |
| | 0 | 566 | | BaseUrl = configuration["ProfitLiga:BaseUrl"] ?? "https://api.pr-lg.ru", |
| | 0 | 567 | | Secret = configuration["ProfitLiga:Secret"] ?? string.Empty |
| | 0 | 568 | | }); |
| | 0 | 569 | | services.AddSingleton<ProfitLigaHttpClient>(); |
| | 0 | 570 | | services.AddTransient<ProfitLigaDataManager>(); |
| | 0 | 571 | | services.AddTransient<IApiSupplierConnector, ProfitLigaSupplierConnector>(); |
| | 0 | 572 | | services.AddTransient<ProfitLigaCartService>(); |
| | 0 | 573 | | services.AddHttpClient("NikeiApi", client => client.Timeout = TimeSpan.FromSeconds(30)) |
| | 0 | 574 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 575 | | services.Configure<NikeiConfig>(configuration.GetSection(NikeiConfig.SectionName)); |
| | 0 | 576 | | services.PostConfigure<NikeiConfig>(config => |
| | 0 | 577 | | { |
| | 0 | 578 | | if (string.IsNullOrWhiteSpace(config.BaseUrl)) config.BaseUrl = "https://nikei.ru"; |
| | 0 | 579 | | }); |
| | 0 | 580 | | services.AddSingleton<NikeiHttpClient>(); |
| | 0 | 581 | | services.AddTransient<NikeiDataManager>(); |
| | 0 | 582 | | services.AddTransient<IApiSupplierConnector, NikeiSupplierConnector>(); |
| | 0 | 583 | | services.AddTransient<NikeiCartService>(); |
| | 0 | 584 | | services.AddHttpClient("MikadoMskHod20Api", client => client.Timeout = TimeSpan.FromSeconds(30)) |
| | 0 | 585 | | .AddPolicyHandler(BaseApiClient.GetRetryPolicy()); |
| | 0 | 586 | | services.AddSingleton(new MikadoConfig |
| | 0 | 587 | | { |
| | 0 | 588 | | BaseUrl = configuration["MikadoMskHod20:BaseUrl"] ?? "https://www.mikado-parts.ru/ws1", |
| | 0 | 589 | | ClientId = configuration["MikadoMskHod20:ClientId"] ?? string.Empty, |
| | 0 | 590 | | Password = configuration["MikadoMskHod20:Password"] ?? string.Empty |
| | 0 | 591 | | }); |
| | 0 | 592 | | services.AddSingleton<MikadoHttpClient>(); |
| | 0 | 593 | | services.AddTransient<MikadoDataManager>(); |
| | 0 | 594 | | services.AddTransient<MikadoCartService>(); |
| | 0 | 595 | | services.AddTransient<IApiSupplierConnector, MikadoSupplierConnector>(); |
| | 0 | 596 | | services.AddHttpClient("ZZapApi", client => client.Timeout = TimeSpan.FromSeconds(60)); |
| | 0 | 597 | | services.AddSingleton(new ZZapConfig |
| | 0 | 598 | | { |
| | 0 | 599 | | BaseUrl = configuration["ZZap:BaseUrl"] ?? "https://b52-api.zzap.pro", |
| | 0 | 600 | | ApiKey = configuration["ZZap:ApiKey"] ?? string.Empty, |
| | 0 | 601 | | CodeRegion = int.TryParse(configuration["ZZap:CodeRegion"], out var zzapRegion) ? zzapRegion : 1, |
| | 0 | 602 | | MinRequestIntervalMs = int.TryParse(configuration["ZZap:MinRequestIntervalMs"], out var zzapInterval) ? zzap |
| | 0 | 603 | | }); |
| | | 604 | | // Без retry-политики: повтор на 429 делает сам клиент с учётом интервала, а слепой retry только |
| | | 605 | | // умножил бы отказы по частоте. |
| | 0 | 606 | | services.AddSingleton<ZZapHttpClient>(); |
| | 0 | 607 | | services.AddTransient<ZZapDataManager>(); |
| | 0 | 608 | | services.AddTransient<IApiSupplierConnector, ZZapMoscowSupplierConnector>(); |
| | 0 | 609 | | services.AddTransient<ZZapCartService>(); |
| | 0 | 610 | | services.AddTransient<ApiSupplierConnectorRegistry>(); |
| | | 611 | | |
| | 0 | 612 | | services.AddSingleton(new InterpartsApiConfig |
| | 0 | 613 | | { |
| | 0 | 614 | | Token = configuration["Interparts:Token"] ?? string.Empty, |
| | 0 | 615 | | BaseUrl = configuration["Interparts:BaseUrl"] ?? string.Empty |
| | 0 | 616 | | }); |
| | | 617 | | |
| | 0 | 618 | | services.AddSingleton<InterpartsDataManager>(); |
| | | 619 | | |
| | | 620 | | // Telegram-бот: токен и чат берутся из секции TelegramBot. RemoveAllLoggers — токен входит в URL |
| | | 621 | | // запроса и не должен попадать в стандартные логи HttpClient. |
| | 0 | 622 | | var telegramBotConfig = configuration.GetSection(TelegramBotConfig.SectionName).Get<TelegramBotConfig>() |
| | 0 | 623 | | ?? new TelegramBotConfig(); |
| | 0 | 624 | | services.AddSingleton(telegramBotConfig); |
| | 0 | 625 | | services.AddHttpClient<TelegramBotApiClient>(client => |
| | 0 | 626 | | { |
| | 0 | 627 | | client.BaseAddress = new Uri(telegramBotConfig.BaseUrl.TrimEnd('/') + "/"); |
| | 0 | 628 | | client.Timeout = TimeSpan.FromSeconds(Math.Max(1, telegramBotConfig.TimeoutSeconds)); |
| | 0 | 629 | | }) |
| | 0 | 630 | | .RemoveAllLoggers(); |
| | 0 | 631 | | services.AddTransient<ITelegramNotifier, TelegramNotifier>(); |
| | 0 | 632 | | services.AddTransient<NewOrdersTelegramNotifier>(); |
| | 0 | 633 | | services.AddTransient<Services.BitrixStockServices.BitrixStockRolloutTelegramReporter>(); |
| | | 634 | | |
| | | 635 | | // OpenRouter AI (OpenAI-compatible API) |
| | 0 | 636 | | var openRouterConfig = new OpenRouterApiConfig |
| | 0 | 637 | | { |
| | 0 | 638 | | ApiKey = configuration["OpenRouter:ApiKey"] ?? string.Empty, |
| | 0 | 639 | | BaseUrl = configuration["OpenRouter:BaseUrl"] ?? "https://openrouter.ai/api/v1", |
| | 0 | 640 | | Model = configuration["OpenRouter:Model"] ?? "openrouter/free", |
| | 0 | 641 | | ChipsModel = configuration["OpenRouter:ChipsModel"] ?? "openrouter/auto", |
| | 0 | 642 | | TimeoutSeconds = int.TryParse(configuration["OpenRouter:TimeoutSeconds"], out var orTimeout) ? orTimeout : 6 |
| | 0 | 643 | | }; |
| | 0 | 644 | | services.AddSingleton(openRouterConfig); |
| | | 645 | | |
| | 0 | 646 | | services.AddHttpClient<OpenRouterApiClient>((sp, client) => |
| | 0 | 647 | | { |
| | 0 | 648 | | var cfg = sp.GetRequiredService<OpenRouterApiConfig>(); |
| | 0 | 649 | | client.BaseAddress = new Uri(cfg.BaseUrl.TrimEnd('/') + "/"); |
| | 0 | 650 | | client.Timeout = TimeSpan.FromSeconds(cfg.TimeoutSeconds); |
| | 0 | 651 | | if (!string.IsNullOrWhiteSpace(cfg.ApiKey)) |
| | 0 | 652 | | { |
| | 0 | 653 | | client.DefaultRequestHeaders.Add("Authorization", $"Bearer {cfg.ApiKey}"); |
| | 0 | 654 | | } |
| | 0 | 655 | | client.DefaultRequestHeaders.Add("HTTP-Referer", "https://studio2.mpsuper.ru"); |
| | 0 | 656 | | client.DefaultRequestHeaders.Add("X-Title", "Studio2 AI Assistant"); |
| | 0 | 657 | | }); |
| | | 658 | | |
| | | 659 | | // Partsnavigator: карточка запчасти для модалки товара в гридах (парсер partsnavigator.com.au, 5 воркеров). |
| | | 660 | | // Ретраев Polly нет намеренно: каждый повтор занимает один из 5 браузерных воркеров сервиса. |
| | 0 | 661 | | var partsnavigatorConfig = configuration.GetSection(PartsnavigatorConfig.SectionName).Get<PartsnavigatorConfig>( |
| | 0 | 662 | | ?? new PartsnavigatorConfig(); |
| | 0 | 663 | | services.AddSingleton(partsnavigatorConfig); |
| | 0 | 664 | | services.AddHttpClient(PartsnavigatorConfig.HttpClientName, client => |
| | 0 | 665 | | { |
| | 0 | 666 | | client.BaseAddress = new Uri(partsnavigatorConfig.BaseUrl.TrimEnd('/') + "/"); |
| | 0 | 667 | | client.Timeout = TimeSpan.FromSeconds(Math.Max(5, partsnavigatorConfig.TimeoutSeconds)); |
| | 0 | 668 | | }); |
| | 0 | 669 | | services.AddSingleton<IPartsnavigatorApiClient, PartsnavigatorApiClient>(); |
| | 0 | 670 | | services.AddSingleton<IPartsnavigatorService, PartsnavigatorService>(); |
| | | 671 | | |
| | | 672 | | // Движок Interparts: предложения по артикулу. Ретраев нет: каждый повтор заново опрашивает поставщиков. |
| | 0 | 673 | | var interpartsEngineConfig = configuration.GetSection(InterpartsEngineConfig.SectionName).Get<InterpartsEngineCo |
| | 0 | 674 | | ?? new InterpartsEngineConfig(); |
| | 0 | 675 | | services.AddSingleton(interpartsEngineConfig); |
| | 0 | 676 | | services.AddHttpClient(InterpartsEngineConfig.HttpClientName, client => |
| | 0 | 677 | | { |
| | 0 | 678 | | client.BaseAddress = new Uri(interpartsEngineConfig.BaseUrl.TrimEnd('/') + "/"); |
| | 0 | 679 | | client.Timeout = TimeSpan.FromSeconds(Math.Max(5, interpartsEngineConfig.TimeoutSeconds)); |
| | 0 | 680 | | if (!interpartsEngineConfig.IsConfigured) return; |
| | 0 | 681 | | client.DefaultRequestHeaders.Add("X-Api-Key", interpartsEngineConfig.ApiKey); |
| | 0 | 682 | | client.DefaultRequestHeaders.Add("X-Customer-Login", interpartsEngineConfig.CustomerLogin); |
| | 0 | 683 | | }); |
| | 0 | 684 | | services.AddSingleton<IInterpartsEngineApiClient, InterpartsEngineApiClient>(); |
| | 0 | 685 | | services.AddSingleton<IInterpartsEngineService, InterpartsEngineService>(); |
| | | 686 | | |
| | 0 | 687 | | services.AddTransient<OpenRouterDataManager>(); |
| | 0 | 688 | | services.AddTransient<AiOrderScopeApplier>(); |
| | 0 | 689 | | services.AddTransient<OrderHistoryPageContextLoader>(); |
| | 0 | 690 | | services.AddTransient<AiProductContextLoader>(); |
| | 0 | 691 | | services.AddTransient<AiChatEnrichmentBuilder>(); |
| | 0 | 692 | | services.AddTransient<AiOzonLiveProductService>(); |
| | 0 | 693 | | services.AddTransient<AiOrderAnalysisService>(); |
| | 0 | 694 | | services.AddTransient<AiOrderWriteService>(); |
| | 0 | 695 | | services.AddTransient<AiOrderSearchAgent>(); |
| | 0 | 696 | | services.AddTransient<AiOrderFilterLlmParser>(); |
| | 0 | 697 | | services.AddTransient<AiOrderFilterQueryStudio>(); |
| | | 698 | | |
| | | 699 | | // Action chip suggestors (порядок → порядок чипов: read раньше nav) |
| | 0 | 700 | | services.AddTransient<IAiActionSuggestor, AiArticleActionSuggestor>(); |
| | 0 | 701 | | services.AddTransient<IAiActionSuggestor, AiOrderActionSuggestor>(); |
| | 0 | 702 | | services.AddTransient<AiActionSuggestorAggregator>(); |
| | | 703 | | |
| | | 704 | | // Services used by Studio2 Orders/transactions controllers. |
| | 0 | 705 | | services.AddHttpClient("ProxyClient"); |
| | 0 | 706 | | services.AddSingleton<UrlResponseCacheService>(); |
| | 0 | 707 | | services.AddTransient<ProxyHttpClientService>(); |
| | | 708 | | |
| | 0 | 709 | | services.AddTransient<AssemblyListService>(); |
| | 0 | 710 | | services.AddScoped<AssemblyListRunRepository>(); |
| | 0 | 711 | | services.AddScoped<AssemblyListGenerationOrchestrator>(); |
| | 0 | 712 | | } |
| | | 713 | | } |