模型注册
This commit is contained in:
1 parent
75d0e85218
commit
0e1620c1f9
11 files changed
+399
-41
No files matched your search
@@ -0,0 +1,61 @@
|
||||
using MAX.Framework.Interfaces;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace MAX.Framework.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// 宿主(Program.cs)入口:模块两阶段装配。
|
||||
/// <code>
|
||||
/// var builder = WebApplication.CreateBuilder(args);
|
||||
/// builder.AddStoreHub(); // 阶段一:服务注册(自动执行所有 IModule.AddModule)
|
||||
///
|
||||
/// var app = builder.Build();
|
||||
/// // ... 宿主自己的中间件、认证、端点 ...
|
||||
/// app.UseStoreHubModules(); // 阶段二:管道装配(自动执行所有 IModule.UseModule)
|
||||
/// app.Run();
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public static class HostBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 在 <see cref="WebApplicationBuilder"/> 上注册应用框架服务:
|
||||
/// 等价于 <c>builder.Services.AddStoreHub(builder.Configuration, builder.Environment)</c>,
|
||||
/// 区别是会把配置与环境一起包装成 <see cref="ModuleContext"/> 交给各模块。
|
||||
/// </summary>
|
||||
public static WebApplicationBuilder AddStoreHub(this WebApplicationBuilder builder)
|
||||
{
|
||||
builder.Services.AddStoreHub(builder.Configuration, builder.Environment);
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行所有模块的管道阶段(<see cref="IModule.UseModule"/>):注册中间件、看板、Map 端点等。
|
||||
/// 必须在 <c>builder.Build()</c> 之后、<c>app.Run()</c> 之前调用;
|
||||
/// 建议放在 <c>UseAuthorization()</c> 之后、<c>MapControllers()</c> 之前。
|
||||
/// </summary>
|
||||
public static WebApplication UseStoreHubModules(this WebApplication app)
|
||||
{
|
||||
// 宿主没调用过 AddStoreHub() 时容器里没有注册表,直接返回即可
|
||||
ModuleRegistry? registry = app.Services.GetService<ModuleRegistry>();
|
||||
if (registry is null)
|
||||
{
|
||||
return app;
|
||||
}
|
||||
|
||||
foreach (IModule module in registry.Modules)
|
||||
{
|
||||
try
|
||||
{
|
||||
module.UseModule(app);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"模块 {module.GetType().FullName} 的 UseModule 执行失败。", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using MAX.Framework.Interfaces;
|
||||
|
||||
namespace MAX.Framework.DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块注册表:保存 <c>AddStoreHub()</c> 阶段发现(并按优先级排序)的模块实例。
|
||||
/// 它被注册进容器,好让 <c>app.UseStoreHubModules()</c> 在 <c>builder.Build()</c> 之后拿到
|
||||
/// 「同一批实例」继续执行管道阶段,而不是重新反射创建一遍。
|
||||
/// </summary>
|
||||
internal sealed class ModuleRegistry
|
||||
{
|
||||
public ModuleRegistry(IReadOnlyList<IModule> modules)
|
||||
{
|
||||
Modules = modules;
|
||||
}
|
||||
|
||||
/// <summary>按优先级降序排列的模块实例</summary>
|
||||
public IReadOnlyList<IModule> Modules { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.DependencyInjection;
|
||||
using MAX.Core.Interfaces;
|
||||
using MAX.Framework.Commons;
|
||||
using MAX.Framework.Dtos;
|
||||
using MAX.Framework.Interfaces;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
@@ -23,18 +25,49 @@ namespace MAX.Framework.DependencyInjection
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// 注册应用服务:
|
||||
/// <list type="number">
|
||||
/// <item>自动注册所有实现 <see cref="ISingletonDependency"/> / <see cref="IScopedDependency"/> / <see cref="ITransientDependency"/> 的类型;</item>
|
||||
/// <item>发现所有 <see cref="IStartup"/>,按 <see cref="IStartup.Priority"/> 降序执行 Initialize + Register。</item>
|
||||
/// </list>
|
||||
/// 兼容入口:只有服务集合,没有配置与环境(模块拿到的 <see cref="ModuleContext.Configuration"/> 是空配置)。
|
||||
/// 新代码请用 <c>builder.AddStoreHub()</c>。
|
||||
/// </summary>
|
||||
public static IServiceCollection AddStoreHub(this IServiceCollection services)
|
||||
{
|
||||
return services.AddStoreHub(null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带配置的注册入口:<c>builder.Services.AddStoreHub(builder.Configuration)</c>
|
||||
/// </summary>
|
||||
public static IServiceCollection AddStoreHub(this IServiceCollection services, IConfiguration? configuration)
|
||||
{
|
||||
return services.AddStoreHub(configuration, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册应用服务:
|
||||
/// <list type="number">
|
||||
/// <item>自动注册所有实现 <see cref="ISingletonDependency"/> / <see cref="IScopedDependency"/> / <see cref="ITransientDependency"/> 的类型;</item>
|
||||
/// <item>发现所有 <see cref="IStartup"/>,按 <see cref="IStartup.Priority"/> 降序执行 Initialize + Register;</item>
|
||||
/// <item>发现所有 <see cref="IModule"/>,按 <see cref="IModule.Priority"/> 降序执行 <see cref="IModule.AddModule"/>,
|
||||
/// 并把模块实例登记进容器,留给 <c>app.UseStoreHubModules()</c> 执行管道阶段。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public static IServiceCollection AddStoreHub(this IServiceCollection services, IConfiguration? configuration, IHostEnvironment? environment)
|
||||
{
|
||||
IEnumerable<Assembly> assemblies = GetApplicationAssemblies();
|
||||
|
||||
// 同一个类可能同时实现 IStartup 与 IModule,这里缓存实例,保证两个阶段拿到的是同一个对象
|
||||
Dictionary<Type, object> instances = [];
|
||||
|
||||
RegisterDependencyMarkers(services, assemblies);
|
||||
RunStartups(services, assemblies);
|
||||
RunModules(services, assemblies);
|
||||
RunStartups(services, assemblies, instances);
|
||||
|
||||
ModuleContext context = new(
|
||||
services,
|
||||
new ServiceRegistrarProxy(services),
|
||||
configuration ?? new ConfigurationBuilder().Build(),
|
||||
environment);
|
||||
|
||||
// 注册表必须在 Build() 之前进容器,UseStoreHubModules() 才拿得到
|
||||
services.AddSingleton(RunModules(assemblies, instances, context));
|
||||
|
||||
|
||||
services.AddControllers(options =>
|
||||
@@ -102,28 +135,55 @@ namespace MAX.Framework.DependencyInjection
|
||||
/// <summary>
|
||||
/// 发现所有 <see cref="IStartup"/> 实现,按 <see cref="IStartup.Priority"/> 降序执行
|
||||
/// </summary>
|
||||
private static void RunStartups(IServiceCollection services, IEnumerable<Assembly> assemblies)
|
||||
private static void RunStartups(IServiceCollection services, IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances)
|
||||
{
|
||||
IServiceRegistrar registrar = new ServiceRegistrarProxy(services);
|
||||
|
||||
foreach (IStartup startup in GetConcreteTypes(assemblies)
|
||||
.Where(t => typeof(IStartup).IsAssignableFrom(t))
|
||||
.Select(t => (IStartup)Activator.CreateInstance(t, true)!)
|
||||
.OrderByDescending(s => s.Priority))
|
||||
foreach (IStartup startup in CreateInstances<IStartup>(assemblies, instances).OrderByDescending(s => s.Priority))
|
||||
{
|
||||
startup.Initialize();
|
||||
startup.Register(registrar);
|
||||
}
|
||||
}
|
||||
private static void RunModules(IServiceCollection services, IEnumerable<Assembly> 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)!))
|
||||
/// <summary>
|
||||
/// 发现所有 <see cref="IModule"/> 实现,按 <see cref="IModule.Priority"/> 降序执行服务注册阶段
|
||||
/// </summary>
|
||||
private static ModuleRegistry RunModules(IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances, ModuleContext context)
|
||||
{
|
||||
List<IModule> modules = [.. CreateInstances<IModule>(assemblies, instances).OrderByDescending(m => m.Priority)];
|
||||
|
||||
foreach (IModule module in modules)
|
||||
{
|
||||
startup.AddModule(services);
|
||||
try
|
||||
{
|
||||
module.AddModule(context);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 带上模块名,避免只看到一个没有上下文的异常
|
||||
throw new InvalidOperationException($"模块 {module.GetType().FullName} 的 AddModule 执行失败。", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return new ModuleRegistry(modules);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反射创建(或复用)所有实现 <typeparamref name="T"/> 的具体类型实例
|
||||
/// </summary>
|
||||
private static IEnumerable<T> CreateInstances<T>(IEnumerable<Assembly> assemblies, IDictionary<Type, object> instances) where T : class
|
||||
{
|
||||
foreach (Type type in GetConcreteTypes(assemblies).Where(t => typeof(T).IsAssignableFrom(t)))
|
||||
{
|
||||
if (!instances.TryGetValue(type, out object? instance))
|
||||
{
|
||||
instance = Activator.CreateInstance(type, true)
|
||||
?? throw new InvalidOperationException($"无法创建 {type.FullName} 的实例(需要有参数量为 0 的构造函数)。");
|
||||
instances[type] = instance;
|
||||
}
|
||||
|
||||
yield return (T)instance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,37 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace MAX.Framework.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 业务模块:把「服务注册」和「管道装配」两件事都交给模块自己,
|
||||
/// 由宿主的两行代码统一驱动(模块不需要出现在 Program.cs 里):
|
||||
/// <list type="number">
|
||||
/// <item><c>builder.AddStoreHub()</c> → 自动发现所有模块并执行 <see cref="AddModule"/>(服务注册阶段,<c>Build()</c> 之前);</item>
|
||||
/// <item><c>app.UseStoreHubModules()</c> → 自动执行 <see cref="UseModule"/>(管道阶段,<c>Build()</c> 之后、<c>Run()</c> 之前)。</item>
|
||||
/// </list>
|
||||
/// 模块实例由反射创建,因此实现类必须有无参构造函数。
|
||||
/// </summary>
|
||||
public interface IModule
|
||||
{
|
||||
void AddModule(IServiceCollection services);
|
||||
/// <summary>
|
||||
/// 模块执行顺序(数值越大越先执行),默认 <see cref="MAX.Core.Interfaces.IntPriority.Normal"/>。
|
||||
/// 两个阶段都按这个顺序执行。
|
||||
/// </summary>
|
||||
int Priority => MAX.Core.Interfaces.IntPriority.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// 服务注册阶段(<c>builder.Build()</c> 之前):注册本模块的服务、选项、后台任务类型。
|
||||
/// 需要读配置时用 <see cref="ModuleContext.Configuration"/>,不要再去碰 <c>builder</c>。
|
||||
/// </summary>
|
||||
void AddModule(ModuleContext context);
|
||||
|
||||
/// <summary>
|
||||
/// 管道装配阶段(<c>builder.Build()</c> 之后、<c>app.Run()</c> 之前):
|
||||
/// 注册中间件(如 Hangfire 看板)、Map 端点、读取 <see cref="WebApplication.Services"/> 解析服务、注册启动任务等。
|
||||
/// 不需要管道装配的模块可以不实现(默认空实现)。
|
||||
/// </summary>
|
||||
void UseModule(WebApplication app)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using MAX.Core.Interfaces;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
|
||||
namespace MAX.Framework.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块基类:给出 <see cref="IModule.Priority"/> 与 <see cref="IModule.UseModule"/> 的默认实现,
|
||||
/// 模块只需重写自己真正关心的阶段(只有服务注册时只写 <see cref="AddModule"/>)。
|
||||
/// </summary>
|
||||
public abstract class ModuleBase : IModule
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public virtual int Priority => IntPriority.Normal;
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void AddModule(ModuleContext context);
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual void UseModule(WebApplication app)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using MAX.Core.DependencyInjection;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace MAX.Framework.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块上下文:模块在「服务注册阶段」能拿到的全部宿主信息。
|
||||
/// 之所以包成一个对象,是为了以后要再补宿主的其他东西(日志、配置节、HostApplicationBuilder 等)时不用改接口签名。
|
||||
/// </summary>
|
||||
public sealed class ModuleContext
|
||||
{
|
||||
public ModuleContext(
|
||||
IServiceCollection services,
|
||||
IServiceRegistrar registrar,
|
||||
IConfiguration configuration,
|
||||
IHostEnvironment? environment = null)
|
||||
{
|
||||
Services = services;
|
||||
Registrar = registrar;
|
||||
Configuration = configuration;
|
||||
Environment = environment;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务集合(Microsoft.Extensions.DependencyInjection),等价于宿主里的 <c>builder.Services</c>
|
||||
/// </summary>
|
||||
public IServiceCollection Services { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 容器无关的注册器,等价于 <see cref="IStartup.Register"/> 里拿到的那个,便于以后换容器
|
||||
/// </summary>
|
||||
public IServiceRegistrar Registrar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用配置,等价于宿主的 <c>builder.Configuration</c>(永不为 null;走旧的无参入口时是空配置)
|
||||
/// </summary>
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 宿主环境,等价于宿主的 <c>builder.Environment</c>;走旧的无参入口时为 null
|
||||
/// </summary>
|
||||
public IHostEnvironment? Environment { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using Hangfire;
|
||||
using Hangfire.MySql;
|
||||
using HangfireBasicAuthenticationFilter;
|
||||
using MAX.Core.Interfaces;
|
||||
using MAX.Framework.Interfaces;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using MySql.Data.MySqlClient;
|
||||
using Store.Modules.CloudProduct.Services;
|
||||
|
||||
namespace Store.Modules.CloudProduct
|
||||
{
|
||||
/// <summary>
|
||||
/// CloudProduct 模块的模块化入口(<see cref="IModule"/>):Hangfire 后台任务(商品分词同步)+ Hangfire 看板。
|
||||
/// 与 <see cref="Startup"/>(只做 IStartup 的 Initialize/Register)分开,是因为模块注册需要配置和 app:
|
||||
/// <list type="bullet">
|
||||
/// <item><c>builder.AddStoreHub()</c> 执行 <see cref="AddModule"/>(注册 Hangfire 服务,此时只能拿到容器和配置);</item>
|
||||
/// <item><c>app.UseStoreHubModules()</c> 执行 <see cref="UseModule"/>(挂看板中间件 + 注册定时任务,此时才有 app)。</item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
public class CloudProductModule : ModuleBase
|
||||
{
|
||||
public override int Priority => IntPriority.Normal;
|
||||
|
||||
/// <summary>
|
||||
/// 服务注册阶段(builder.Build() 之前):这里只能拿到容器和配置,碰不到 app
|
||||
/// </summary>
|
||||
public override void AddModule(ModuleContext context)
|
||||
{
|
||||
IServiceCollection services = context.Services;
|
||||
IConfiguration configuration = context.Configuration;
|
||||
|
||||
// ===== Hangfire 后台定时任务(商品分词同步) =====
|
||||
var hangfireConnection = configuration.GetConnectionString("DefaultConnection")
|
||||
?? throw new InvalidOperationException("缺少配置 ConnectionStrings:DefaultConnection,CloudProduct 模块的 Hangfire 无法启动。");
|
||||
|
||||
// AllowUserVariables=true:Hangfire.MySqlStorage 的建表/查询语句依赖用户变量
|
||||
var connectionStringBuilder = new MySqlConnectionStringBuilder(hangfireConnection)
|
||||
{
|
||||
AllowUserVariables = true
|
||||
};
|
||||
|
||||
services.AddHangfire(config => config
|
||||
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
|
||||
.UseSimpleAssemblyNameTypeSerializer()
|
||||
.UseRecommendedSerializerSettings()
|
||||
.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)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 注册定时任务:把未同步(IndexStatus=0)的商品写入 OpenSearch 分词索引
|
||||
var cron = app.Configuration.GetValue<string>("Indexing:Cron") ?? "*/5 * * * *";
|
||||
|
||||
// 用容器里的 IRecurringJobManager,而不是静态 RecurringJob:
|
||||
// AddHangfire 只把 JobStorage 注册进容器(没有设置 JobStorage.Current),静态 API 在 Build() 之后调用会直接抛异常。
|
||||
app.Services.GetRequiredService<IRecurringJobManager>().AddOrUpdate<GoodsIndexJob>(
|
||||
"goods-index-pending",
|
||||
job => job.ProcessPendingAsync(),
|
||||
cron);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 数据库不可用/表未初始化时避免阻断应用启动(看板与定时任务都会连库),恢复后重启即可重新装配
|
||||
Console.WriteLine("[Hangfire] 看板/定时任务装配失败: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Store.Modules.CloudProduct.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Hangfire 定时任务:把数据库中未同步(IndexStatus=0)的商品写入 OpenSearch 分词索引。
|
||||
/// </summary>
|
||||
public class GoodsIndexJob
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<GoodsIndexJob> _logger;
|
||||
|
||||
public GoodsIndexJob(IServiceScopeFactory scopeFactory, IConfiguration configuration,ILogger<GoodsIndexJob> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task ProcessPendingAsync()
|
||||
{
|
||||
var batchSize = _configuration.GetValue<int?>("Indexing:BatchSize") ?? 1000;
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var service = scope.ServiceProvider.GetRequiredService<GoodsService>();
|
||||
var count = await service.IndexPendingGoodsAsync(batchSize);
|
||||
_logger.LogInformation($"[Hangfire] 商品分词定时任务完成,本次同步 {count} 条");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,23 @@
|
||||
using MAX.Core.DependencyInjection;
|
||||
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
|
||||
/// <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;
|
||||
|
||||
public void AddModule(IServiceCollection services)
|
||||
{
|
||||
services.AddControllers();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Register(IServiceRegistrar registrar)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
@@ -12,6 +12,9 @@
|
||||
<ProjectReference Include="..\..\Store.Framework\Store.Framework.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.25" />
|
||||
<PackageReference Include="Hangfire.Dashboard.Basic.Authentication" Version="8.0.0" />
|
||||
<PackageReference Include="Hangfire.MySqlStorage" Version="2.0.3" />
|
||||
<PackageReference Include="OpenSearch.Client" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Asp.Versioning;
|
||||
using Asp.Versioning;
|
||||
using MAX.Framework.DependencyInjection;
|
||||
using Scalar.AspNetCore;
|
||||
|
||||
@@ -8,8 +8,9 @@ public partial class Program
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// IOC 注册:自动注册 ISingletonDependency / IScopedDependency / ITransientDependency,并执行所有 IStartup(实现见 StoreHub.Framework)
|
||||
builder.Services.AddStoreHub();
|
||||
// IOC 注册:自动注册 ISingletonDependency / IScopedDependency / ITransientDependency,并执行所有 IStartup / IModule.AddModule
|
||||
// 模块服务注册阶段(等价于 builder.Services.AddStoreHub(builder.Configuration, builder.Environment),额外把配置/环境交给模块)
|
||||
builder.AddStoreHub();
|
||||
builder.Services.AddFreeSqlDB();
|
||||
// JWT 验证:注册 JwtBearer 认证 + 令牌服务 + OpenAPI Bearer 安全方案(配置节 Jwt)
|
||||
builder.Services.AddStoreHubJwt(builder.Configuration);
|
||||
@@ -62,6 +63,9 @@ public partial class Program
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
// 模块管道装配阶段:执行所有 IModule.UseModule(Hangfire 看板、定时任务等),必须在 Run() 之前
|
||||
app.UseStoreHubModules();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
|
||||
Reference in new issue
Block a user