gx 配置
This commit is contained in:
1 parent
0e1620c1f9
commit
c732ecb1d2
7 files changed
+166
-16
No files matched your search
@@ -1,14 +1,17 @@
|
|||||||
using MAX.Framework.Interfaces;
|
using MAX.Framework.Interfaces;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Configuration.Json;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace MAX.Framework.DependencyInjection
|
namespace MAX.Framework.DependencyInjection
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 宿主(Program.cs)入口:模块两阶段装配。
|
/// 宿主(Program.cs)入口:模块两阶段装配 + 模块自己的配置文件。
|
||||||
/// <code>
|
/// <code>
|
||||||
/// var builder = WebApplication.CreateBuilder(args);
|
/// var builder = WebApplication.CreateBuilder(args);
|
||||||
/// builder.AddStoreHub(); // 阶段一:服务注册(自动执行所有 IModule.AddModule)
|
/// builder.AddStoreHub(); // 阶段一:加载各模块配置文件 + 自动执行所有 IModule.AddModule
|
||||||
///
|
///
|
||||||
/// var app = builder.Build();
|
/// var app = builder.Build();
|
||||||
/// // ... 宿主自己的中间件、认证、端点 ...
|
/// // ... 宿主自己的中间件、认证、端点 ...
|
||||||
@@ -20,12 +23,27 @@ namespace MAX.Framework.DependencyInjection
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 在 <see cref="WebApplicationBuilder"/> 上注册应用框架服务:
|
/// 在 <see cref="WebApplicationBuilder"/> 上注册应用框架服务:
|
||||||
/// 等价于 <c>builder.Services.AddStoreHub(builder.Configuration, builder.Environment)</c>,
|
/// <list type="number">
|
||||||
/// 区别是会把配置与环境一起包装成 <see cref="ModuleContext"/> 交给各模块。
|
/// <item>发现所有 <see cref="IModule"/>;</item>
|
||||||
|
/// <item>把每个模块声明的 <see cref="IModule.ConfigurationFile"/> 并入宿主配置树(宿主 appsettings.json 不需要放模块的配置);</item>
|
||||||
|
/// <item>再执行 <c>builder.Services.AddStoreHub(builder.Configuration, builder.Environment)</c>。</item>
|
||||||
|
/// </list>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static WebApplicationBuilder AddStoreHub(this WebApplicationBuilder builder)
|
public static WebApplicationBuilder AddStoreHub(this WebApplicationBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Services.AddStoreHub(builder.Configuration, builder.Environment);
|
IEnumerable<Assembly> assemblies = ServiceCollectionExtensions.GetApplicationAssemblies();
|
||||||
|
|
||||||
|
// 同一个类可能同时实现 IStartup 与 IModule,这里缓存实例,保证后面两个阶段拿到的是同一个对象
|
||||||
|
Dictionary<Type, object> instances = [];
|
||||||
|
ModuleRegistry modules = ServiceCollectionExtensions.DiscoverModules(assemblies, instances);
|
||||||
|
|
||||||
|
// 必须在跑 AddModule 之前并入,模块注册时才读得到自己的配置节
|
||||||
|
foreach (IModule module in modules.Modules)
|
||||||
|
{
|
||||||
|
AddModuleConfigurationFile(builder.Configuration, module);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Services.AddStoreHubCore(assemblies, modules, instances, builder.Configuration, builder.Environment);
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,5 +75,51 @@ namespace MAX.Framework.DependencyInjection
|
|||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 把模块自己的配置文件并进宿主配置:插在 appsettings*.json 之后、环境变量/命令行之前,
|
||||||
|
/// 这样模块配置能覆盖宿主 appsettings.json,同时仍然允许用环境变量/命令行覆盖它。
|
||||||
|
/// </summary>
|
||||||
|
private static void AddModuleConfigurationFile(IConfigurationBuilder configuration, IModule module)
|
||||||
|
{
|
||||||
|
string? fileName = module.ConfigurationFile;
|
||||||
|
if (string.IsNullOrWhiteSpace(fileName))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string path = Path.IsPathRooted(fileName)
|
||||||
|
? fileName
|
||||||
|
: Path.Combine(AppContext.BaseDirectory, fileName);
|
||||||
|
|
||||||
|
JsonConfigurationSource source = new()
|
||||||
|
{
|
||||||
|
Path = path,
|
||||||
|
Optional = true, // 文件不存在时不报错(模块没配就是没有)
|
||||||
|
ReloadOnChange = true // 允许改配置不重启(与 appsettings.json 行为一致)
|
||||||
|
};
|
||||||
|
source.ResolveFileProvider();
|
||||||
|
|
||||||
|
IList<IConfigurationSource> sources = configuration.Sources;
|
||||||
|
int index = sources.Count;
|
||||||
|
|
||||||
|
// 找到最后一个 appsettings*.json,插到它后面
|
||||||
|
for (int i = sources.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (sources[i] is FileConfigurationSource file
|
||||||
|
&& Path.GetFileName(file.Path).StartsWith("appsettings", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
index = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sources.Insert(index, source);
|
||||||
|
|
||||||
|
if (configuration is IConfigurationRoot root)
|
||||||
|
{
|
||||||
|
root.Reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,8 +25,8 @@ namespace MAX.Framework.DependencyInjection
|
|||||||
];
|
];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 兼容入口:只有服务集合,没有配置与环境(模块拿到的 <see cref="ModuleContext.Configuration"/> 是空配置)。
|
/// 兼容入口:只有服务集合,没有配置与环境(模块拿到的 <see cref="ModuleContext.Configuration"/> 是空配置),
|
||||||
/// 新代码请用 <c>builder.AddStoreHub()</c>。
|
/// 也拿不到 IConfigurationBuilder,所以模块自己的配置文件不会加载。新代码请用 <c>builder.AddStoreHub()</c>。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IServiceCollection AddStoreHub(this IServiceCollection services)
|
public static IServiceCollection AddStoreHub(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
@@ -49,6 +49,7 @@ namespace MAX.Framework.DependencyInjection
|
|||||||
/// <item>发现所有 <see cref="IModule"/>,按 <see cref="IModule.Priority"/> 降序执行 <see cref="IModule.AddModule"/>,
|
/// <item>发现所有 <see cref="IModule"/>,按 <see cref="IModule.Priority"/> 降序执行 <see cref="IModule.AddModule"/>,
|
||||||
/// 并把模块实例登记进容器,留给 <c>app.UseStoreHubModules()</c> 执行管道阶段。</item>
|
/// 并把模块实例登记进容器,留给 <c>app.UseStoreHubModules()</c> 执行管道阶段。</item>
|
||||||
/// </list>
|
/// </list>
|
||||||
|
/// 这个入口不加载模块自己的配置文件,需要模块配置请用 <c>builder.AddStoreHub()</c>。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static IServiceCollection AddStoreHub(this IServiceCollection services, IConfiguration? configuration, IHostEnvironment? environment)
|
public static IServiceCollection AddStoreHub(this IServiceCollection services, IConfiguration? configuration, IHostEnvironment? environment)
|
||||||
{
|
{
|
||||||
@@ -56,7 +57,22 @@ namespace MAX.Framework.DependencyInjection
|
|||||||
|
|
||||||
// 同一个类可能同时实现 IStartup 与 IModule,这里缓存实例,保证两个阶段拿到的是同一个对象
|
// 同一个类可能同时实现 IStartup 与 IModule,这里缓存实例,保证两个阶段拿到的是同一个对象
|
||||||
Dictionary<Type, object> instances = [];
|
Dictionary<Type, object> instances = [];
|
||||||
|
ModuleRegistry modules = DiscoverModules(assemblies, instances);
|
||||||
|
|
||||||
|
return services.AddStoreHubCore(assemblies, modules, instances, configuration, environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注册应用服务的公共实现:宿主入口(<c>builder.AddStoreHub()</c>)与 <see cref="IServiceCollection"/> 入口共用。
|
||||||
|
/// </summary>
|
||||||
|
internal static IServiceCollection AddStoreHubCore(
|
||||||
|
this IServiceCollection services,
|
||||||
|
IEnumerable<Assembly> assemblies,
|
||||||
|
ModuleRegistry modules,
|
||||||
|
IDictionary<Type, object> instances,
|
||||||
|
IConfiguration? configuration,
|
||||||
|
IHostEnvironment? environment)
|
||||||
|
{
|
||||||
RegisterDependencyMarkers(services, assemblies);
|
RegisterDependencyMarkers(services, assemblies);
|
||||||
RunStartups(services, assemblies, instances);
|
RunStartups(services, assemblies, instances);
|
||||||
|
|
||||||
@@ -66,8 +82,10 @@ namespace MAX.Framework.DependencyInjection
|
|||||||
configuration ?? new ConfigurationBuilder().Build(),
|
configuration ?? new ConfigurationBuilder().Build(),
|
||||||
environment);
|
environment);
|
||||||
|
|
||||||
|
RunModules(modules, context);
|
||||||
|
|
||||||
// 注册表必须在 Build() 之前进容器,UseStoreHubModules() 才拿得到
|
// 注册表必须在 Build() 之前进容器,UseStoreHubModules() 才拿得到
|
||||||
services.AddSingleton(RunModules(assemblies, instances, context));
|
services.AddSingleton(modules);
|
||||||
|
|
||||||
|
|
||||||
services.AddControllers(options =>
|
services.AddControllers(options =>
|
||||||
@@ -147,13 +165,19 @@ namespace MAX.Framework.DependencyInjection
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发现所有 <see cref="IModule"/> 实现,按 <see cref="IModule.Priority"/> 降序执行服务注册阶段
|
/// 发现所有 <see cref="IModule"/> 实现,只创建实例,不执行任何阶段
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static ModuleRegistry RunModules(IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances, ModuleContext context)
|
internal static ModuleRegistry DiscoverModules(IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances)
|
||||||
{
|
{
|
||||||
List<IModule> modules = [.. CreateInstances<IModule>(assemblies, instances).OrderByDescending(m => m.Priority)];
|
return new ModuleRegistry([.. CreateInstances<IModule>(assemblies, instances).OrderByDescending(m => m.Priority)]);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (IModule module in modules)
|
/// <summary>
|
||||||
|
/// 执行所有模块的服务注册阶段(<see cref="IModule.AddModule"/>)
|
||||||
|
/// </summary>
|
||||||
|
private static void RunModules(ModuleRegistry modules, ModuleContext context)
|
||||||
|
{
|
||||||
|
foreach (IModule module in modules.Modules)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -165,8 +189,6 @@ namespace MAX.Framework.DependencyInjection
|
|||||||
throw new InvalidOperationException($"模块 {module.GetType().FullName} 的 AddModule 执行失败。", ex);
|
throw new InvalidOperationException($"模块 {module.GetType().FullName} 的 AddModule 执行失败。", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ModuleRegistry(modules);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -206,7 +228,10 @@ namespace MAX.Framework.DependencyInjection
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<Assembly> GetApplicationAssemblies()
|
/// <summary>
|
||||||
|
/// 应用程序集(模块、Startup 都从这里发现)
|
||||||
|
/// </summary>
|
||||||
|
internal static IEnumerable<Assembly> GetApplicationAssemblies()
|
||||||
{
|
{
|
||||||
// 使用 AppTypeFinder:扫描应用输出目录 + 已加载程序集,
|
// 使用 AppTypeFinder:扫描应用输出目录 + 已加载程序集,
|
||||||
return StoreHubAppTypeFinder.Current.GetAssemblies();
|
return StoreHubAppTypeFinder.Current.GetAssemblies();
|
||||||
|
|||||||
@@ -19,6 +19,16 @@ namespace MAX.Framework.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
int Priority => MAX.Core.Interfaces.IntPriority.Normal;
|
int Priority => MAX.Core.Interfaces.IntPriority.Normal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 模块自己的配置文件(相对宿主输出目录 <see cref="AppContext.BaseDirectory"/>),
|
||||||
|
/// 例如 "appsettings.CloudProduct.json";默认 null 表示该模块没有独立配置文件。
|
||||||
|
/// 该文件由 <c>builder.AddStoreHub()</c> 自动并入宿主配置树(插在 appsettings*.json 之后、环境变量/命令行之前),
|
||||||
|
/// 因此模块内的服务照常注入 <c>IConfiguration</c> 就能读到自己的配置节,模块之间互不干扰。
|
||||||
|
/// 注意:只有 <c>builder.AddStoreHub()</c> 入口能加载模块配置文件,
|
||||||
|
/// 直接用 <c>builder.Services.AddStoreHub()</c> 时拿不到 IConfigurationBuilder,模块配置不会生效。
|
||||||
|
/// </summary>
|
||||||
|
string? ConfigurationFile => null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 服务注册阶段(<c>builder.Build()</c> 之前):注册本模块的服务、选项、后台任务类型。
|
/// 服务注册阶段(<c>builder.Build()</c> 之前):注册本模块的服务、选项、后台任务类型。
|
||||||
/// 需要读配置时用 <see cref="ModuleContext.Configuration"/>,不要再去碰 <c>builder</c>。
|
/// 需要读配置时用 <see cref="ModuleContext.Configuration"/>,不要再去碰 <c>builder</c>。
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ namespace MAX.Framework.Interfaces
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual int Priority => IntPriority.Normal;
|
public virtual int Priority => IntPriority.Normal;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public virtual string? ConfigurationFile => null;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public abstract void AddModule(ModuleContext context);
|
public abstract void AddModule(ModuleContext context);
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,16 @@ namespace Store.Modules.CloudProduct
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CloudProductModule : ModuleBase
|
public class CloudProductModule : ModuleBase
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 本模块自己的配置文件(源码里是 Models/CloudProduct/appsettings.json,
|
||||||
|
/// 构建时复制到宿主输出目录并改名成这个名字,避免与宿主的 appsettings.json 冲突)
|
||||||
|
/// </summary>
|
||||||
|
public const string ConfigurationFileName = "appsettings.CloudProduct.json";
|
||||||
|
|
||||||
public override int Priority => IntPriority.Normal;
|
public override int Priority => IntPriority.Normal;
|
||||||
|
|
||||||
|
public override string? ConfigurationFile => ConfigurationFileName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 服务注册阶段(builder.Build() 之前):这里只能拿到容器和配置,碰不到 app
|
/// 服务注册阶段(builder.Build() 之前):这里只能拿到容器和配置,碰不到 app
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -31,6 +39,12 @@ namespace Store.Modules.CloudProduct
|
|||||||
IServiceCollection services = context.Services;
|
IServiceCollection services = context.Services;
|
||||||
IConfiguration configuration = context.Configuration;
|
IConfiguration configuration = context.Configuration;
|
||||||
|
|
||||||
|
// 本模块的配置节(Search / RawData / Indexing / Delete)都在模块自己的配置文件里,
|
||||||
|
// 关键项缺失直接报错,避免 OpenSearch 地址为空、原始数据写到意外目录之类的隐性故障
|
||||||
|
RequireConfiguration(configuration, "Search:Url");
|
||||||
|
RequireConfiguration(configuration, "Search:IndexName");
|
||||||
|
RequireConfiguration(configuration, "RawData:Path");
|
||||||
|
|
||||||
// ===== Hangfire 后台定时任务(商品分词同步) =====
|
// ===== Hangfire 后台定时任务(商品分词同步) =====
|
||||||
var hangfireConnection = configuration.GetConnectionString("DefaultConnection")
|
var hangfireConnection = configuration.GetConnectionString("DefaultConnection")
|
||||||
?? throw new InvalidOperationException("缺少配置 ConnectionStrings:DefaultConnection,CloudProduct 模块的 Hangfire 无法启动。");
|
?? throw new InvalidOperationException("缺少配置 ConnectionStrings:DefaultConnection,CloudProduct 模块的 Hangfire 无法启动。");
|
||||||
@@ -85,5 +99,16 @@ namespace Store.Modules.CloudProduct
|
|||||||
Console.WriteLine("[Hangfire] 看板/定时任务装配失败: " + ex.Message);
|
Console.WriteLine("[Hangfire] 看板/定时任务装配失败: " + ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 校验模块配置项存在(值来自模块自己的 <see cref="ConfigurationFileName"/>)
|
||||||
|
/// </summary>
|
||||||
|
private static void RequireConfiguration(IConfiguration configuration, string key)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(configuration.GetValue<string>(key)))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"缺少配置 {key},请检查模块配置文件 {ConfigurationFileName}。");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
@@ -11,6 +11,12 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Store.Framework\Store.Framework.csproj" />
|
<ProjectReference Include="..\..\Store.Framework\Store.Framework.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- 本模块自己的配置文件:源码里叫 appsettings.json,构建后复制到宿主输出目录时改名为
|
||||||
|
appsettings.CloudProduct.json,避免和宿主自己的 appsettings.json 互相覆盖。
|
||||||
|
由 builder.AddStoreHub() 自动并入宿主配置树(在 appsettings*.json 之后、环境变量之前)。 -->
|
||||||
|
<None Update="appsettings.json" Link="appsettings.CloudProduct.json" CopyToOutputDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.25" />
|
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.25" />
|
||||||
<PackageReference Include="Hangfire.Dashboard.Basic.Authentication" Version="8.0.0" />
|
<PackageReference Include="Hangfire.Dashboard.Basic.Authentication" Version="8.0.0" />
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"Search": {
|
||||||
|
"Url": "http://192.168.0.200:9200",
|
||||||
|
"IndexName": "goods_search"
|
||||||
|
},
|
||||||
|
"RawData": {
|
||||||
|
"Path": "E:\\GoodsRawData"
|
||||||
|
},
|
||||||
|
"Indexing": {
|
||||||
|
"Mode": "Background",
|
||||||
|
"Cron": "*/5 * * * *",
|
||||||
|
"BatchSize": 20000
|
||||||
|
},
|
||||||
|
"Delete": {
|
||||||
|
"SoftDelete": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user