修改 Startup 加载 和Module 加载
This commit is contained in:
1 parent
c732ecb1d2
commit
c7fb447034
17 files changed
+462
-383
No files matched your search
@@ -6,8 +6,10 @@ using MAX.Framework.Interfaces;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using MySql.Data.MySqlClient;
|
||||
using Store.Modules.CloudProduct.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Store.Modules.CloudProduct
|
||||
{
|
||||
@@ -19,7 +21,7 @@ namespace Store.Modules.CloudProduct
|
||||
/// <item><c>app.UseStoreHubModules()</c> 执行 <see cref="UseModule"/>(挂看板中间件 + 注册定时任务,此时才有 app)。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class CloudProductModule : ModuleBase
|
||||
public class CloudProductModule : IModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 本模块自己的配置文件(源码里是 Models/CloudProduct/appsettings.json,
|
||||
@@ -27,23 +29,31 @@ namespace Store.Modules.CloudProduct
|
||||
/// </summary>
|
||||
public const string ConfigurationFileName = "appsettings.CloudProduct.json";
|
||||
|
||||
public override int Priority => IntPriority.Normal;
|
||||
public int Priority => IntPriority.Normal;
|
||||
|
||||
public override string? ConfigurationFile => ConfigurationFileName;
|
||||
|
||||
/// <summary>
|
||||
/// 服务注册阶段(builder.Build() 之前):这里只能拿到容器和配置,碰不到 app
|
||||
/// </summary>
|
||||
public override void AddModule(ModuleContext context)
|
||||
public void AddModule(IHostApplicationBuilder host)
|
||||
{
|
||||
IServiceCollection services = context.Services;
|
||||
IConfiguration configuration = context.Configuration;
|
||||
IServiceCollection services = host.Services;
|
||||
IConfigurationManager configuration = host.Configuration;
|
||||
|
||||
// 本模块的配置节(Search / RawData / Indexing / Delete)都在模块自己的配置文件里,
|
||||
// 关键项缺失直接报错,避免 OpenSearch 地址为空、原始数据写到意外目录之类的隐性故障
|
||||
if (!string.IsNullOrWhiteSpace(ConfigurationFileName))
|
||||
{
|
||||
string path = Path.IsPathRooted(ConfigurationFileName)? ConfigurationFileName: Path.Combine(AppContext.BaseDirectory, ConfigurationFileName);
|
||||
configuration.AddJsonFile(path, optional: false, reloadOnChange: true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//// 本模块的配置节(Search / RawData / Indexing / Delete)都在模块自己的配置文件里,
|
||||
//// 关键项缺失直接报错,避免 OpenSearch 地址为空、原始数据写到意外目录之类的隐性故障
|
||||
RequireConfiguration(configuration, "Search:Url");
|
||||
RequireConfiguration(configuration, "Search:IndexName");
|
||||
RequireConfiguration(configuration, "RawData:Path");
|
||||
//RequireConfiguration(configuration, "Search:IndexName");
|
||||
//RequireConfiguration(configuration, "RawData:Path");
|
||||
|
||||
// ===== Hangfire 后台定时任务(商品分词同步) =====
|
||||
var hangfireConnection = configuration.GetConnectionString("DefaultConnection")
|
||||
@@ -62,33 +72,35 @@ namespace Store.Modules.CloudProduct
|
||||
.UseStorage(new MySqlStorage(connectionStringBuilder.ConnectionString, new MySqlStorageOptions() { TablesPrefix = "Hangfire_" })));
|
||||
services.AddHangfireServer();
|
||||
services.AddTransient<GoodsIndexJob>();
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 管道装配阶段(builder.Build() 之后、app.Run() 之前):这里可以挂中间件、解析容器里的服务
|
||||
/// </summary>
|
||||
public override void UseModule(WebApplication app)
|
||||
public void UseModule(IApplicationBuilder app)
|
||||
{
|
||||
try
|
||||
{
|
||||
var Configuration = app.ApplicationServices.GetService<IConfiguration>()!;
|
||||
// Hangfire 后台任务看板(默认地址 /hangfire)
|
||||
app.UseHangfireDashboard("/hangfire", new DashboardOptions
|
||||
{
|
||||
Authorization = new[]
|
||||
{
|
||||
new HangfireCustomBasicAuthenticationFilter(){
|
||||
User = app.Configuration.GetValue<string>("Hangfire:Dashboard:User") ?? "admin",
|
||||
Pass = app.Configuration.GetValue<string>("Hangfire:Dashboard:Pass") ?? "1234560"
|
||||
User = Configuration.GetValue<string>("Hangfire:Dashboard:User") ?? "admin",
|
||||
Pass = Configuration.GetValue<string>("Hangfire:Dashboard:Pass") ?? "1234560"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 注册定时任务:把未同步(IndexStatus=0)的商品写入 OpenSearch 分词索引
|
||||
var cron = app.Configuration.GetValue<string>("Indexing:Cron") ?? "*/5 * * * *";
|
||||
var cron = Configuration.GetValue<string>("Indexing:Cron") ?? "*/5 * * * *";
|
||||
|
||||
// 用容器里的 IRecurringJobManager,而不是静态 RecurringJob:
|
||||
// AddHangfire 只把 JobStorage 注册进容器(没有设置 JobStorage.Current),静态 API 在 Build() 之后调用会直接抛异常。
|
||||
app.Services.GetRequiredService<IRecurringJobManager>().AddOrUpdate<GoodsIndexJob>(
|
||||
app.ApplicationServices.GetRequiredService<IRecurringJobManager>().AddOrUpdate<GoodsIndexJob>(
|
||||
"goods-index-pending",
|
||||
job => job.ProcessPendingAsync(),
|
||||
cron);
|
||||
|
||||
@@ -246,6 +246,65 @@ namespace Store.Modules.CloudProduct.Controllers
|
||||
return await _searchService.GetStatusAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查搜索索引是否存在。
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<bool> IndexExists()
|
||||
{
|
||||
return await _searchService.IndexExistsAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查索引映射(分析器)是否符合当前服务要求。
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
public async Task<bool> IndexMappingHealthy()
|
||||
{
|
||||
return await _searchService.IsIndexMappingHealthyAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建搜索索引(含 IK 分词映射,已存在且映射正常时跳过)。
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task<bool> CreateIndex()
|
||||
{
|
||||
return await _searchService.CreateIndexAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除搜索索引。
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task DeleteIndex()
|
||||
{
|
||||
await _searchService.DeleteIndexAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动刷新搜索索引,使最新写入立即可搜索。
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task RefreshIndex()
|
||||
{
|
||||
await _searchService.RefreshIndexAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动新增或覆盖一条搜索文档。
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
public async Task AddSearchDocument([FromBody] GoodsSearchDocument document)
|
||||
{
|
||||
if (document == null || document.Id <= 0)
|
||||
{
|
||||
throw new AppException("请提供有效的搜索文档", ResultCode.ValidationError);
|
||||
}
|
||||
|
||||
await _searchService.AddDocumentAsync(document);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取指定商品详情:解压缩原始数据并返回 JSON。
|
||||
/// </summary>
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Store.Modules.CloudProduct.Dtos
|
||||
namespace Store.Modules.CloudProduct.Dtos
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenSearch 运行状态
|
||||
@@ -24,18 +20,88 @@ namespace Store.Modules.CloudProduct.Dtos
|
||||
/// </summary>
|
||||
public bool Connected { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OpenSearch 服务及目标索引是否健康
|
||||
/// </summary>
|
||||
public bool Healthy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 集群名称
|
||||
/// </summary>
|
||||
public string? ClusterName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 集群状态(green、yellow 或 red)
|
||||
/// </summary>
|
||||
public string? ClusterStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 集群节点总数
|
||||
/// </summary>
|
||||
public int NumberOfNodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据节点数量
|
||||
/// </summary>
|
||||
public int NumberOfDataNodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 活跃主分片数量
|
||||
/// </summary>
|
||||
public int ActivePrimaryShards { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 活跃分片总数
|
||||
/// </summary>
|
||||
public int ActiveShards { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 正在迁移的分片数量
|
||||
/// </summary>
|
||||
public int RelocatingShards { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 正在初始化的分片数量
|
||||
/// </summary>
|
||||
public int InitializingShards { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 未分配的分片数量
|
||||
/// </summary>
|
||||
public int UnassignedShards { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 索引是否存在
|
||||
/// </summary>
|
||||
public bool IndexExists { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文档总数(索引不存在时为 -1)
|
||||
/// 索引映射是否符合当前服务要求
|
||||
/// </summary>
|
||||
public bool MappingHealthy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标题字段的索引分析器
|
||||
/// </summary>
|
||||
public string? IndexAnalyzer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标题字段的搜索分析器
|
||||
/// </summary>
|
||||
public string? SearchAnalyzer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文档总数(索引不存在或读取失败时为 -1)
|
||||
/// </summary>
|
||||
public long DocumentCount { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 说明信息
|
||||
/// 状态检测耗时(毫秒)
|
||||
/// </summary>
|
||||
public long ElapsedMilliseconds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态说明或错误信息
|
||||
/// </summary>
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Diagnostics;
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Framework.Commons;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using OpenSearch.Client;
|
||||
using OpenSearch.Net;
|
||||
using Store.Modules.CloudProduct.Dtos;
|
||||
@@ -19,6 +21,9 @@ namespace Store.Modules.CloudProduct.Services
|
||||
Task DeleteDocumentsAsync(IEnumerable<long> ids);
|
||||
Task DeleteIndexAsync();
|
||||
Task<OpenSearchStatusDto> GetStatusAsync();
|
||||
Task<bool> IndexExistsAsync();
|
||||
Task<bool> IsIndexMappingHealthyAsync();
|
||||
Task RefreshIndexAsync();
|
||||
Task<long> RebuildIndexAsync(IEnumerable<GoodsEntity> goodsList);
|
||||
Task<(long Total, List<GoodsSearchDocument> Documents)> SearchByTitleAsync(GoodsSearchRequest request, GoodsSearchSortBy sortBy = GoodsSearchSortBy.Relevance);
|
||||
}
|
||||
@@ -33,13 +38,15 @@ namespace Store.Modules.CloudProduct.Services
|
||||
private static readonly TimeSpan QueryTimeout = TimeSpan.FromSeconds(5);
|
||||
// 批量写入/建索引/删除索引等管理类操作超时(数据量大时需要更长,可挪到配置文件)
|
||||
private static readonly TimeSpan AdminTimeout = TimeSpan.FromSeconds(30);
|
||||
private readonly ILogger<OpenSearchService> _logger;
|
||||
|
||||
public OpenSearchService(IConfiguration configuration)
|
||||
public OpenSearchService(IConfiguration configuration,ILogger<OpenSearchService> logger)
|
||||
{
|
||||
var config = configuration.GetSection("Search");
|
||||
_nodeUrl = config.GetValue<string>("Url") ?? "";
|
||||
_indexName = config.GetValue<string>("IndexName") ?? "";
|
||||
_client = CreateClient();
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private IOpenSearchClient CreateClient()
|
||||
@@ -100,34 +107,38 @@ namespace Store.Modules.CloudProduct.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验索引映射是否健康:title 字段必须使用 IK 分析器
|
||||
/// </summary>
|
||||
private async Task<bool> IsIndexMappingHealthyAsync()
|
||||
public async Task<bool> 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 mappingStatus = await GetIndexMappingStatusAsync();
|
||||
return mappingStatus.Healthy;
|
||||
}
|
||||
|
||||
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
|
||||
private async Task<(bool Healthy, string? IndexAnalyzer, string? SearchAnalyzer)> GetIndexMappingStatusAsync()
|
||||
{
|
||||
var getResponse = await _client.Indices.GetAsync(_indexName, d => d
|
||||
.RequestConfiguration(r => r.RequestTimeout(QueryTimeout)));
|
||||
if (!getResponse.IsValid)
|
||||
{
|
||||
return false;
|
||||
throw new AppException("读取索引映射失败: " + getResponse.DebugInformation);
|
||||
}
|
||||
if (!getResponse.Indices.TryGetValue(_indexName, out var indexState))
|
||||
{
|
||||
return (false, null, null);
|
||||
}
|
||||
|
||||
var mapping = indexState.Mappings;
|
||||
if (mapping?.Properties == null
|
||||
|| !mapping.Properties.TryGetValue(new PropertyName("title"), out var property)
|
||||
|| property is not ITextProperty textProperty)
|
||||
{
|
||||
return (false, null, null);
|
||||
}
|
||||
|
||||
var indexAnalyzer = textProperty.Analyzer;
|
||||
var searchAnalyzer = textProperty.SearchAnalyzer;
|
||||
var usesIk = indexAnalyzer == "ik_max_word" && searchAnalyzer == "ik_smart";
|
||||
var usesStandard = indexAnalyzer == "standard" && searchAnalyzer == "standard";
|
||||
return (usesIk || usesStandard, indexAnalyzer, searchAnalyzer);
|
||||
}
|
||||
|
||||
private async Task<CreateIndexResponse> TryCreateIndexAsync(bool useIk)
|
||||
@@ -173,6 +184,23 @@ namespace Store.Modules.CloudProduct.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> IndexExistsAsync()
|
||||
{
|
||||
var response = await _client.Indices.ExistsAsync(_indexName, d => d
|
||||
.RequestConfiguration(r => r.RequestTimeout(QueryTimeout)));
|
||||
return response.Exists;
|
||||
}
|
||||
|
||||
public async Task RefreshIndexAsync()
|
||||
{
|
||||
var response = await _client.Indices.RefreshAsync(_indexName, d => d
|
||||
.RequestConfiguration(r => r.RequestTimeout(AdminTimeout)));
|
||||
if (!response.IsValid)
|
||||
{
|
||||
throw new AppException("刷新索引失败: " + response.DebugInformation);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ============ 3.1 批量写入商品搜索文档(存在则覆盖) ============
|
||||
/// </summary>
|
||||
@@ -198,8 +226,7 @@ namespace Store.Modules.CloudProduct.Services
|
||||
}
|
||||
|
||||
// 刷新索引,保证数据立即可搜索(仅用于演示,生产环境不建议频繁 Refresh)
|
||||
await _client.Indices.RefreshAsync(_indexName, d => d
|
||||
.RequestConfiguration(r => r.RequestTimeout(AdminTimeout)));
|
||||
await RefreshIndexAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -260,8 +287,7 @@ namespace Store.Modules.CloudProduct.Services
|
||||
throw new AppException("删除 OpenSearch 文档失败: " + response.DebugInformation);
|
||||
}
|
||||
|
||||
await _client.Indices.RefreshAsync(_indexName, d => d
|
||||
.RequestConfiguration(r => r.RequestTimeout(AdminTimeout)));
|
||||
await RefreshIndexAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -279,8 +305,7 @@ namespace Store.Modules.CloudProduct.Services
|
||||
|
||||
if (response.IsValid)
|
||||
{
|
||||
await _client.Indices.RefreshAsync(_indexName, d => d
|
||||
.RequestConfiguration(r => r.RequestTimeout(QueryTimeout)));
|
||||
await RefreshIndexAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -414,6 +439,7 @@ namespace Store.Modules.CloudProduct.Services
|
||||
/// </summary>
|
||||
public async Task<OpenSearchStatusDto> GetStatusAsync()
|
||||
{
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
var status = new OpenSearchStatusDto
|
||||
{
|
||||
NodeUrl = _nodeUrl,
|
||||
@@ -428,28 +454,65 @@ namespace Store.Modules.CloudProduct.Services
|
||||
if (!ping.IsValid)
|
||||
{
|
||||
status.Message = ping.DebugInformation;
|
||||
_logger.LogError(ping.DebugInformation);
|
||||
return status;
|
||||
}
|
||||
_logger.LogInformation("OpenSerach 已连接");
|
||||
var clusterHealth = await _client.Cluster.HealthAsync(Indices.All, h => h
|
||||
.RequestConfiguration(r => r.RequestTimeout(QueryTimeout)));
|
||||
if (!clusterHealth.IsValid)
|
||||
{
|
||||
status.Message = clusterHealth.DebugInformation;
|
||||
return status;
|
||||
}
|
||||
|
||||
var exists = await _client.Indices.ExistsAsync(_indexName, d => d
|
||||
.RequestConfiguration(r => r.RequestTimeout(QueryTimeout)));
|
||||
status.IndexExists = exists.Exists;
|
||||
status.ClusterName = clusterHealth.ClusterName;
|
||||
status.ClusterStatus = clusterHealth.Status.ToString().ToLowerInvariant();
|
||||
status.NumberOfNodes = clusterHealth.NumberOfNodes;
|
||||
status.NumberOfDataNodes = clusterHealth.NumberOfDataNodes;
|
||||
status.ActivePrimaryShards = clusterHealth.ActivePrimaryShards;
|
||||
status.ActiveShards = clusterHealth.ActiveShards;
|
||||
status.RelocatingShards = clusterHealth.RelocatingShards;
|
||||
status.InitializingShards = clusterHealth.InitializingShards;
|
||||
status.UnassignedShards = clusterHealth.UnassignedShards;
|
||||
status.IndexExists = await IndexExistsAsync();
|
||||
|
||||
if (exists.Exists)
|
||||
if (status.IndexExists)
|
||||
{
|
||||
var mappingStatus = await GetIndexMappingStatusAsync();
|
||||
status.MappingHealthy = mappingStatus.Healthy;
|
||||
status.IndexAnalyzer = mappingStatus.IndexAnalyzer;
|
||||
status.SearchAnalyzer = mappingStatus.SearchAnalyzer;
|
||||
|
||||
var count = await _client.CountAsync<GoodsSearchDocument>(c => c
|
||||
.Index(_indexName)
|
||||
.RequestConfiguration(r => r.RequestTimeout(QueryTimeout)));
|
||||
status.DocumentCount = count.IsValid ? count.Count : -1;
|
||||
if (!count.IsValid)
|
||||
{
|
||||
status.Message = count.DebugInformation;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
status.Message = $"索引 [{_indexName}] 不存在";
|
||||
}
|
||||
|
||||
status.Healthy = status.IndexExists
|
||||
&& status.MappingHealthy
|
||||
&& status.ClusterStatus != "red"
|
||||
&& status.DocumentCount >= 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
status.Connected = false;
|
||||
status.Healthy = false;
|
||||
status.Message = ex.Message;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
status.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Interfaces;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Store.Modules.CloudProduct.Services;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Store.Modules.CloudProduct
|
||||
{
|
||||
/// <summary>
|
||||
/// CloudProduct 模块的启动器(<see cref="IStartup"/>):由 AddStoreHub() 自动发现,
|
||||
/// 按 <see cref="Priority"/> 降序执行 <see cref="Initialize"/> + <see cref="Register"/>。
|
||||
/// 需要配置或管道装配(app)的部分不在这里,见 <see cref="CloudProductModule"/>。
|
||||
/// </summary>
|
||||
|
||||
public class Startup : IStartup
|
||||
{
|
||||
public int Priority => IntPriority.Normal;
|
||||
@@ -19,5 +20,13 @@ namespace Store.Modules.CloudProduct
|
||||
public void Register(IServiceRegistrar registrar)
|
||||
{
|
||||
}
|
||||
|
||||
public async void Start(IServiceProvider serviceProvider)
|
||||
{
|
||||
var openSearchService = serviceProvider.GetRequiredService<OpenSearchService>();
|
||||
await openSearchService.GetStatusAsync();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user