diff --git a/Runtime/App.razor b/Runtime/App.razor index 429dbb7..465dd44 100644 --- a/Runtime/App.razor +++ b/Runtime/App.razor @@ -1,15 +1,16 @@ -@using Quantum.Infrastructure.Models +@using Quantum.Runtime.Services @inject InjectedCodeManager CodeManager + Quantum - - + + @foreach (var markupCode in CodeManager.HeadCodes) { @@ -30,6 +31,7 @@ { @markupCode } + @* ReSharper disable once Html.PathError *@ @foreach (var markupCode in CodeManager.PostBlazorCodes) { @@ -37,4 +39,4 @@ } - \ No newline at end of file + diff --git a/Runtime/Components/NavMenu.razor b/Runtime/Components/NavMenu.razor index 14b20ef..db2b78c 100644 --- a/Runtime/Components/NavMenu.razor +++ b/Runtime/Components/NavMenu.razor @@ -1,4 +1,4 @@ -@using Quantum.Infrastructure.Abstractions +@using Quantum.Sdk @inject NavigationManager NavigationManager @inject IEnumerable Modules @@ -7,7 +7,7 @@ Class="nav-menu" SelectedKeys="@(new[] { _currentModuleKey })" OnMenuItemClicked="OnMenuItemClicked"> - @foreach (var module in Modules.OrderBy(m => m.ModuleTitle)) + @foreach (var module in Modules) { } - - - diff --git a/Runtime/Components/Sidebar.razor b/Runtime/Components/Sidebar.razor index 46855a9..6ee1856 100644 --- a/Runtime/Components/Sidebar.razor +++ b/Runtime/Components/Sidebar.razor @@ -1,5 +1,4 @@ -@using Quantum.Infrastructure.Abstractions -@using Quantum.Infrastructure.Models +@using Quantum.Sdk @inject IEnumerable Modules @inject NavigationManager NavigationManager diff --git a/Runtime/Components/TitleBar.razor b/Runtime/Components/TitleBar.razor index 1331413..e64f977 100644 --- a/Runtime/Components/TitleBar.razor +++ b/Runtime/Components/TitleBar.razor @@ -1,6 +1,7 @@ -@using ElectronNET.API +@using Quantum.Sdk @inject IJSRuntime Js @inject NavigationManager NavigationManager +@inject IQuantum Quantum
@@ -9,9 +10,9 @@
+DataSource="@Modules" +HidePagination> @@ -30,26 +31,28 @@ @code { + private const string UninstallMarkFile = "TobeUninstalled.Quantum.MarkTag"; private async Task InstallModule() { try { - var mainWindow = Electron.WindowManager.BrowserWindows.First(); + var mainWindow = Quantum.Window; + if(Quantum.Window is null){ return; } var options = new OpenDialogOptions { Properties = @@ -88,12 +91,12 @@ var pendingPath = Path.Combine(Directory.GetCurrentDirectory(), "PendingModule", moduleName); // 确保PendingModule目录存在 - Directory.CreateDirectory(Path.GetDirectoryName(pendingPath) + Directory.CreateDirectory(Path.GetDirectoryName(pendingPath) ?? throw new InvalidOperationException($"无法获取目录名称: {pendingPath}")); - + if (Directory.Exists(pendingPath)) Directory.Delete(pendingPath, true); - + // 使用复制代替移动,因为可能跨卷 CopyDirectory(moduleFolder, pendingPath, true); Directory.Delete(moduleFolder, true); @@ -138,8 +141,8 @@ { var relativePath = Path.GetRelativePath(sourceDir, file); var targetPath = Path.Combine(targetDir, relativePath); - Directory.CreateDirectory(Path.GetDirectoryName(targetPath) - ?? throw new InvalidOperationException($"无法获取目录名称: {targetPath}")); + Directory.CreateDirectory(Path.GetDirectoryName(targetPath) + ?? throw new InvalidOperationException($"无法获取目录名称: {targetPath}")); File.Copy(file, targetPath, overwrite); } } diff --git a/Runtime/Pages/Splash.razor b/Runtime/Pages/Splash.razor index 792e9f7..98b5216 100644 --- a/Runtime/Pages/Splash.razor +++ b/Runtime/Pages/Splash.razor @@ -1,9 +1,9 @@ @page "/" -@using System.Text.Json.Serialization -@using Quantum.Infrastructure.Utilities -@using System.Diagnostics.CodeAnalysis @using Quantum.Infrastructure.Abstractions @using Quantum.Runtime.Components +@using System.Diagnostics.CodeAnalysis +@using System.Text.Json.Serialization +@using Quantum.Sdk.Utilities @inject NavigationManager NavigationManager @inject IEnumerable InitializableServices @@ -27,7 +27,7 @@
@_hitokoto.Content
- —— @_hitokoto.From + —— @_hitokoto.From @if (!string.IsNullOrWhiteSpace(_hitokoto.FromWho)) { 「@_hitokoto.FromWho」 @@ -102,7 +102,7 @@ private async Task InitializeApplicationAsync() { UpdateProgress("正在初始化服务...", 10); - + var services = InitializableServices.ToList(); if (!services.Any()) { diff --git a/Runtime/Program.cs b/Runtime/Program.cs index b3169ca..93dc1d4 100644 --- a/Runtime/Program.cs +++ b/Runtime/Program.cs @@ -1,7 +1,8 @@ using ElectronNET.API; using ElectronNET.API.Entities; -using Quantum.Infrastructure.Models; -using Quantum.Shell.Services; +using Quantum.Runtime.Services; +using Quantum.Sdk; + #if RELEASE using Serilog; @@ -34,6 +35,31 @@ builder.Host.UseSerilog(); #endif +var preloadServices = new ServiceCollection(); +preloadServices.AddLogging() + .AddSingleton() + .AddSingleton(sp => + { + var logger = sp.GetRequiredService>(); + var moduleManager = new ModuleManager(logger) { Activator = sp, HostServices = builder.Services }; + return moduleManager; + }) + .AddSingleton(sp => + { + var serviceManager = new ServiceManager(builder.Services); + return serviceManager; + }) + .AddSingleton(); + +#pragma warning disable ASP0000 // Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices' +var preloadProvider = preloadServices.BuildServiceProvider(); +#pragma warning restore ASP0000 // Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices' + +var quantum = preloadProvider.GetRequiredService(); +var injectedCodeManager = preloadProvider.GetRequiredService(); +var moduleManager = preloadProvider.GetRequiredService(); +await moduleManager.LoadModulesAsync(); + // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); @@ -48,27 +74,15 @@ }); }); -var codeManager = new InjectedCodeManager(); - builder.Services.AddAntDesign() - .AddSingleton(codeManager) - .AddSingleton() - .AddSingleton(builder.Services); - -#pragma warning disable ASP0000 // Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices' -var provider = builder.Services.BuildServiceProvider(); -#pragma warning restore ASP0000 // Do not call 'IServiceCollection.BuildServiceProvider' in 'ConfigureServices' - -var loader = provider.GetRequiredService(); -loader.ServiceProvider = provider; + .AddSingleton(quantum) + .AddSingleton(injectedCodeManager); #region MODULE_DEBUG // 在这里手动加载模块,方便调试 // loader.LoadModule(typeof(IModule).Assembly); #endregion -await loader.LoadModulesAsync(); - var app = builder.Build(); // Configure the HTTP request pipeline. @@ -93,7 +107,7 @@ app.MapRazorComponents() .AddInteractiveServerRenderMode() - .AddAdditionalAssemblies([.. loader.LoadedAssemblies]); + .AddAdditionalAssemblies([.. moduleManager.LoadedAssemblies]); if (HybridSupport.IsElectronActive) { @@ -115,6 +129,7 @@ #endif } }); + quantum.Window = window; window.RemoveMenu(); diff --git a/Runtime/Routes.razor b/Runtime/Routes.razor index ecb3525..c89d1e1 100644 --- a/Runtime/Routes.razor +++ b/Runtime/Routes.razor @@ -1,7 +1,6 @@ -@using Quantum.Infrastructure.Utilities -@using Quantum.Infrastructure.Abstractions @using Quantum.Infrastructure.Extensions @using Quantum.Runtime.Layout +@using Quantum.Sdk @inject IEnumerable Modules @@ -16,7 +15,7 @@

404

页面不存在

抱歉,您访问的页面不存在或已被移除。

- 返回首页 + 返回首页
diff --git a/Runtime/Services/InjectedCodeManager.cs b/Runtime/Services/InjectedCodeManager.cs new file mode 100644 index 0000000..02c6b2a --- /dev/null +++ b/Runtime/Services/InjectedCodeManager.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Components; +using Quantum.Infrastructure.Models; + +namespace Quantum.Runtime.Services; + +/// +/// 管理注入到App.razor的代码片段 +/// +internal class InjectedCodeManager : IInjectedCodeManager +{ + /// + /// 添加到head部分的代码 + /// + public List HeadCodes { get; } = []; + + /// + /// 添加到blazor.web.js之前的代码 + /// + public List PreBlazorCodes { get; } = []; + + + /// + /// 添加到blazor.web.js之后的代码 + /// + public List PostBlazorCodes { get; } = []; + + /// + /// 添加代码到head部分 + /// + /// HTML代码 + public void AddToHead(string code) => HeadCodes.Add(new MarkupString(code)); + + /// + /// 添加代码到blazor.web.js之前 + /// + /// HTML代码 + public void AddPreBlazor(string code) => PreBlazorCodes.Add(new MarkupString(code)); + + /// + /// 添加代码到blazor.web.js之后 + /// + /// HTML代码 + public void AddPostBlazor(string code) => PostBlazorCodes.Add(new MarkupString(code)); +} diff --git a/Runtime/Services/ModuleLoader.cs b/Runtime/Services/ModuleManager.cs similarity index 66% rename from Runtime/Services/ModuleLoader.cs rename to Runtime/Services/ModuleManager.cs index 1f7e399..6cd5c94 100644 --- a/Runtime/Services/ModuleLoader.cs +++ b/Runtime/Services/ModuleManager.cs @@ -1,17 +1,18 @@ -using Quantum.Infrastructure.Abstractions; -using Quantum.Infrastructure.Models; +using Quantum.Sdk; +using Quantum.Sdk.Services; +using Quantum.Sdk.Utilities; using System.Reflection; -namespace Quantum.Shell.Services; +namespace Quantum.Runtime.Services; -internal class ModuleLoader(ILogger logger, IServiceCollection services, InjectedCodeManager codeManager) +internal class ModuleManager(ILogger logger) : IModuleManager { private readonly List _loadedModules = []; private readonly List _loadedAssemblies = []; - public IServiceProvider ServiceProvider { get; set; } = null!; + public required IServiceCollection HostServices { get; init; } + public required IServiceProvider Activator { get; init; } public IReadOnlyList LoadedAssemblies => _loadedAssemblies.AsReadOnly(); - public async Task LoadModulesAsync() { // 扫描模块目录 @@ -42,7 +43,7 @@ public async Task LoadModulesAsync() // 调用所有模块的 OnAllLoaded 方法 var initTasks = _loadedModules.Select(module => - module.OnAllLoadedAsync(_loadedModules)).ToList(); + module.OnAllLoadedAsync()).ToList(); await Task.WhenAll(initTasks); @@ -73,32 +74,24 @@ private void RegisterModule(Assembly assembly) var moduleType = moduleTypes[0]; // 创建模块实例并添加到已加载模块列表 - var module = (IModule)ActivatorUtilities.CreateInstance(ServiceProvider, moduleType); + var module = (IModule)ActivatorUtilities.CreateInstance(Activator, moduleType); _loadedModules.Add(module); - // 注册模块类型本身 - services.AddSingleton(moduleType, module); - // 注册为 IModule - services.AddSingleton(module); - - // 如果实现了 IUiModule,额外注册 - if (typeof(IUiModule).IsAssignableFrom(moduleType)) - { - var moduleName = assembly.GetName().Name; - var styleFile = $"_content/{moduleName}/{moduleName}.styles.css"; - var stylePath = Path.Combine(AppContext.BaseDirectory, "wwwroot", styleFile); - - if (File.Exists(stylePath)) - { - codeManager.AddToHead($""); - logger.LogInformation("Injected stylesheet for module {ModuleId}: {StyleFile}", module.ModuleId, styleFile); - } - services.AddSingleton((IUiModule)module); - } + HostServices.AddSingleton(module); - logger.LogInformation("Registered module {ModuleId} of type {ModuleType}", - module.ModuleId, moduleType.FullName); + logger.LogInformation("Registered module {ModuleId} of type {ModuleType}", module.ModuleId, moduleType.FullName); + } + public Result GetModule(string moduleId, Version? minVersion = null, Version? maxVersion = null) + { + var module = _loadedModules.FirstOrDefault(m => m.ModuleId == moduleId); + if (module is null) + { return Result.Failure($"{moduleId} is not loaded"); } + if (minVersion is not null && module.Version < minVersion) + { return Result.Failure($"{moduleId} version is less than required version {minVersion}"); } + if (maxVersion is not null && module.Version > maxVersion) + { return Result.Failure($"{moduleId} version is greater than required version {maxVersion}"); } + return Result.Success(module); } } diff --git a/Runtime/Services/Quantum.cs b/Runtime/Services/Quantum.cs new file mode 100644 index 0000000..0266463 --- /dev/null +++ b/Runtime/Services/Quantum.cs @@ -0,0 +1,14 @@ +using ElectronNET.API; +using Quantum.Infrastructure.Models; +using Quantum.Sdk; +using Quantum.Sdk.Services; + +namespace Quantum.Runtime.Services; + +internal class Quantum(ModuleManager moduleManager, InjectedCodeManager injectedCodeManager, ServiceManager serviceManager) : IQuantum +{ + public BrowserWindow? Window { get; set; } + public IServiceManager ServiceManager => serviceManager; + public IModuleManager ModuleManager => moduleManager; + public IInjectedCodeManager InjectedCodeManager => injectedCodeManager; +} diff --git a/Runtime/Services/ServiceManager.cs b/Runtime/Services/ServiceManager.cs new file mode 100644 index 0000000..017c544 --- /dev/null +++ b/Runtime/Services/ServiceManager.cs @@ -0,0 +1,13 @@ +using Quantum.Infrastructure.Abstractions; +using Quantum.Sdk.Services; + +namespace Quantum.Runtime.Services; + +internal class ServiceManager(IServiceCollection services) : IServiceManager +{ + public IServiceManager AddEagerInitializeService(IInitializableService service) + { + services.AddSingleton(service); + return this; + } +} diff --git a/SDK/Quantum.Sdk/Abstractions/IAccountService.cs b/SDK/Quantum.Sdk/Abstractions/IAccountService.cs deleted file mode 100644 index a31fdde..0000000 --- a/SDK/Quantum.Sdk/Abstractions/IAccountService.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Quantum.Infrastructure.Utilities; - -namespace Quantum.Infrastructure.Abstractions; - -public interface IAccountService -{ - string ServiceName { get; } - string LoginRoute { get; } - string LoginStatus { get; } - bool IsAuthenticated { get; } - Task LogoutAsync(); - event Action OnLogout; - public Task> GetAuthencatedClientAsync(RequestOptions? options = null); -} diff --git a/SDK/Quantum.Sdk/Abstractions/IInitializableService.cs b/SDK/Quantum.Sdk/Abstractions/IInitializableService.cs index 7f66646..b8ca842 100644 --- a/SDK/Quantum.Sdk/Abstractions/IInitializableService.cs +++ b/SDK/Quantum.Sdk/Abstractions/IInitializableService.cs @@ -1,6 +1,17 @@ namespace Quantum.Infrastructure.Abstractions; + +/// +/// 可初始化服务接口 +/// public interface IInitializableService { + /// + /// 获取服务是否已初始化 + /// bool IsInitialized { get; } + + /// + /// 获取初始化任务 + /// Task InitializeTask { get; } } diff --git a/SDK/Quantum.Sdk/Abstractions/IPersistentService.cs b/SDK/Quantum.Sdk/Abstractions/IPersistentService.cs index be59abe..b378023 100644 --- a/SDK/Quantum.Sdk/Abstractions/IPersistentService.cs +++ b/SDK/Quantum.Sdk/Abstractions/IPersistentService.cs @@ -1,7 +1,19 @@ namespace Quantum.Infrastructure.Abstractions; +/// +/// 持久化服务接口 +/// public interface IPersistentService { + /// + /// 异步加载状态 + /// + /// 表示异步操作的任务 Task LoadStateAsync(); + + /// + /// 异步保存状态 + /// + /// 表示异步操作的任务 Task SaveStateAsync(); } diff --git a/SDK/Quantum.Sdk/Abstractions/ISnapshotable.cs b/SDK/Quantum.Sdk/Abstractions/ISnapshotable.cs deleted file mode 100644 index a718690..0000000 --- a/SDK/Quantum.Sdk/Abstractions/ISnapshotable.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Quantum.Infrastructure.Abstractions; - -public interface ISnapshotable -{ - T CreateSnapshot(); -} diff --git a/SDK/Quantum.Sdk/Abstractions/IStatefulService.cs b/SDK/Quantum.Sdk/Abstractions/IStatefulService.cs deleted file mode 100644 index 427e5d7..0000000 --- a/SDK/Quantum.Sdk/Abstractions/IStatefulService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Quantum.Infrastructure.Abstractions; - -public interface IStatefulService -{ - T? State { get; set; } - event Action? OnStateChanged; -} diff --git a/SDK/Quantum.Sdk/Abstractions/IUiModule.cs b/SDK/Quantum.Sdk/Abstractions/IUiModule.cs deleted file mode 100644 index 3852cb8..0000000 --- a/SDK/Quantum.Sdk/Abstractions/IUiModule.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Quantum.Infrastructure.Models; - -namespace Quantum.Infrastructure.Abstractions; - -/// -/// 表示一个UI模块接口 -/// -public interface IUiModule : IModule -{ - /// - /// 获取模块标题 - /// - string ModuleTitle { get; } - - /// - /// 获取模块图标 - /// - string? ModuleIcon { get; } - - /// - /// 获取默认页面 - /// - string DefaultRoute { get; } - - /// - /// 获取导航项列表 - /// - /// 有序的导航项列表 - IEnumerable GetNavigationItems(); -} diff --git a/SDK/Quantum.Sdk/Extensions/EnumrableExtension.cs b/SDK/Quantum.Sdk/Extensions/EnumrableExtension.cs index 2add439..37d1daa 100644 --- a/SDK/Quantum.Sdk/Extensions/EnumrableExtension.cs +++ b/SDK/Quantum.Sdk/Extensions/EnumrableExtension.cs @@ -1,11 +1,18 @@ -using Quantum.Infrastructure.Abstractions; -using Quantum.Infrastructure.Utilities; using System.Reflection; namespace Quantum.Infrastructure.Extensions; +/// +/// 可枚举类型扩展方法类 +/// public static class EnumrableExtension { + /// + /// 获取对象集合中每个对象的程序集 + /// + /// 对象类型 + /// 对象集合 + /// 程序集集合 public static IEnumerable GetAssemblies(this IEnumerable objs) { foreach (var obj in objs) @@ -14,17 +21,4 @@ public static IEnumerable GetAssemblies(this IEnumerable objs) yield return type.Assembly; } } - - public static Result GetModule(this IEnumerable modules, string moduleId, Version? minVersion = null, Version? maxVersion = null) - where T : IModule - { - var module = modules.FirstOrDefault(m => m.ModuleId == moduleId); - if (module is null) - { return Result.Failure($"{moduleId} is not loaded"); } - if (minVersion is not null && module.Version < minVersion) - { return Result.Failure($"{moduleId} version is less than required version {minVersion}"); } - if (maxVersion is not null && module.Version > maxVersion) - { return Result.Failure($"{moduleId} version is greater than required version {maxVersion}"); } - return Result.Success(module); - } } diff --git a/SDK/Quantum.Sdk/Extensions/MessageServiceExtension.cs b/SDK/Quantum.Sdk/Extensions/MessageServiceExtension.cs index 7e82fb3..f7e32e3 100644 --- a/SDK/Quantum.Sdk/Extensions/MessageServiceExtension.cs +++ b/SDK/Quantum.Sdk/Extensions/MessageServiceExtension.cs @@ -1,6 +1,10 @@ using AntDesign; namespace Quantum.Infrastructure.Extensions; + +/// +/// 消息服务扩展方法类 +/// public static class MessageServiceExtension { /// @@ -10,7 +14,7 @@ public static class MessageServiceExtension /// 消息服务 /// 要执行的异步操作 /// 加载时显示的消息 - /// 异步操作完成时执行的操作" + /// 异步操作完成时执行的操作 /// 加载消息关闭时执行的操作 /// 异步操作的结果 public static async Task LoadingWhen( @@ -40,7 +44,7 @@ public static async Task LoadingWhen( /// 消息服务 /// 要执行的异步操作 /// 加载时显示的消息 - /// 异步操作完成时执行的操作" + /// 异步操作完成时执行的操作 /// 加载消息关闭时执行的操作 public static async Task LoadingWhen( this IMessageService messageService, diff --git a/SDK/Quantum.Sdk/Extensions/ServiceCollectionExtension.cs b/SDK/Quantum.Sdk/Extensions/ServiceCollectionExtension.cs deleted file mode 100644 index 64082df..0000000 --- a/SDK/Quantum.Sdk/Extensions/ServiceCollectionExtension.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Quantum.Infrastructure.Abstractions; - -namespace Quantum.Infrastructure.Extensions; - -public static class ServiceCollectionExtension -{ - public static IServiceCollection AddEagerInitializeService(this IServiceCollection services) - where TService : class, IInitializableService - where TImplement : class, TService - { - services.TryAddScoped(); - return services.AddScoped(sp => sp.GetRequiredService()) - .AddScoped(sp => sp.GetRequiredService()); - } - - public static IServiceCollection AddAccountService(this IServiceCollection services) - where TService : class, IAccountService - where TImplement : class, TService - { - services.TryAddScoped(); - return services.AddScoped(sp => sp.GetRequiredService()) - .AddScoped(sp => sp.GetRequiredService()); - } -} diff --git a/SDK/Quantum.Sdk/Abstractions/IModule.cs b/SDK/Quantum.Sdk/IModule.cs similarity index 53% rename from SDK/Quantum.Sdk/Abstractions/IModule.cs rename to SDK/Quantum.Sdk/IModule.cs index 6fc6ab1..ec82370 100644 --- a/SDK/Quantum.Sdk/Abstractions/IModule.cs +++ b/SDK/Quantum.Sdk/IModule.cs @@ -1,36 +1,35 @@ -using Quantum.Infrastructure.Utilities; +using Quantum.Sdk.Utilities; -namespace Quantum.Infrastructure.Abstractions; +namespace Quantum.Sdk; /// -/// 表示一个基础模块接口 +/// 表示一个模块接口 /// public interface IModule { /// - /// 获取模块的唯一标识符 + /// 模块的唯一标识符 /// string ModuleId { get; } /// - /// 获取模块的版本号 + /// 模块的版本号 /// Version Version { get; } /// - /// 获取模块的作者 + /// 模块的作者 /// string Author { get; } /// - /// 获取模块的介绍 + /// 模块的介绍 /// string Description { get; } /// /// 当所有模块加载完成时执行的异步逻辑 /// - /// 所有已加载的模块 - /// 表示异步操作结果的任务,包含一个布尔值,指示模块是否已就绪 - Task OnAllLoadedAsync(IEnumerable modules); + /// 表示异步操作结果的任务,包含一个Result,表示模块是否已就绪及对应的消息 + Task OnAllLoadedAsync(); } diff --git a/SDK/Quantum.Sdk/IQuantum.cs b/SDK/Quantum.Sdk/IQuantum.cs new file mode 100644 index 0000000..bfead8d --- /dev/null +++ b/SDK/Quantum.Sdk/IQuantum.cs @@ -0,0 +1,31 @@ +using ElectronNET.API; +using Quantum.Infrastructure.Models; +using Quantum.Sdk.Services; + +namespace Quantum.Sdk; + +/// +/// Quantum应用程序的主接口 +/// +public interface IQuantum +{ + /// + /// 获取主窗口实例 + /// + BrowserWindow? Window { get; } + + /// + /// 获取服务管理器实例 + /// + IServiceManager ServiceManager { get; } + + /// + /// 获取模块管理器实例 + /// + IModuleManager ModuleManager { get; } + + /// + /// 获取注入代码管理器实例 + /// + IInjectedCodeManager InjectedCodeManager { get; } +} diff --git a/SDK/Quantum.Sdk/IUiModule.cs b/SDK/Quantum.Sdk/IUiModule.cs new file mode 100644 index 0000000..f9cdc41 --- /dev/null +++ b/SDK/Quantum.Sdk/IUiModule.cs @@ -0,0 +1,59 @@ +namespace Quantum.Sdk; + +/// +/// 表示一个UI模块接口 +/// +public interface IUiModule : IModule +{ + /// + /// 获取模块标题 + /// + string ModuleTitle { get; } + + /// + /// 获取模块图标 + /// + string? ModuleIcon { get; } + + /// + /// 获取默认页面 + /// + string DefaultRoute { get; } + + /// + /// 获取导航项列表 + /// + /// 有序的导航项列表 + IEnumerable GetNavigationItems(); +} + +/// +/// 表示一个导航项 +/// +public class NavigationItem +{ + /// + /// 菜单项的唯一标识 + /// + public string Key { get; init; } = Guid.NewGuid().ToString(); + + /// + /// 菜单项的显示标题 + /// + public required string Title { get; init; } + + /// + /// 菜单项的图标 + /// + public required string Icon { get; init; } + + /// + /// 菜单项的路由 + /// + public required string Route { get; init; } + + /// + /// 子菜单项 + /// + public IEnumerable Children { get; init; } = []; +} diff --git a/SDK/Quantum.Sdk/Models/AuthenticatedComponentBase.cs b/SDK/Quantum.Sdk/Models/AuthenticatedComponentBase.cs deleted file mode 100644 index 026ae6b..0000000 --- a/SDK/Quantum.Sdk/Models/AuthenticatedComponentBase.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Quantum.Infrastructure.Abstractions; - -namespace Quantum.Infrastructure.Models; - -public abstract class AuthenticatedComponentBase : ComponentBase -{ - [Inject] protected NavigationManager NavigationManager { get; set; } = default!; - - protected abstract IAccountService AccountService { get; } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (!AccountService.IsAuthenticated) - { - NavigationManager.NavigateTo("/accounts"); - } - - await base.OnAfterRenderAsync(firstRender); - } -} diff --git a/SDK/Quantum.Sdk/Models/Entity.cs b/SDK/Quantum.Sdk/Models/Entity.cs deleted file mode 100644 index de8f972..0000000 --- a/SDK/Quantum.Sdk/Models/Entity.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Quantum.Infrastructure.Models; - -public abstract class Entity -{ - public required T Id { get; init; } - - public override bool Equals(object? other) - { - if (other is Entity entity) - { - return EqualityComparer.Default.Equals(Id, entity.Id); - } - return false; - } - - public override int GetHashCode() => HashCode.Combine(Id); -} diff --git a/SDK/Quantum.Sdk/Models/InjectedCodeManager.cs b/SDK/Quantum.Sdk/Models/InjectedCodeManager.cs deleted file mode 100644 index ff2705a..0000000 --- a/SDK/Quantum.Sdk/Models/InjectedCodeManager.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Microsoft.AspNetCore.Components; - -namespace Quantum.Infrastructure.Models; - -/// -/// 管理注入到App.razor的代码片段 -/// -public class InjectedCodeManager -{ - private readonly List _headCodes = []; - private readonly List _preBlazorCodes = []; - private readonly List _postBlazorCodes = []; - - /// - /// 添加到head部分的代码 - /// - public IReadOnlyList HeadCodes => _headCodes; - - /// - /// 添加到blazor.web.js之前的代码 - /// - public IReadOnlyList PreBlazorCodes => _preBlazorCodes; - - /// - /// 添加到blazor.web.js之后的代码 - /// - public IReadOnlyList PostBlazorCodes => _postBlazorCodes; - - /// - /// 添加代码到head部分 - /// - /// HTML代码 - public void AddToHead(string code) => _headCodes.Add(new MarkupString(code)); - - /// - /// 添加代码到blazor.web.js之前 - /// - /// HTML代码 - public void AddPreBlazor(string code) => _preBlazorCodes.Add(new MarkupString(code)); - - /// - /// 添加代码到blazor.web.js之后 - /// - /// HTML代码 - public void AddPostBlazor(string code) => _postBlazorCodes.Add(new MarkupString(code)); -} diff --git a/SDK/Quantum.Sdk/Models/LoginPageBase.cs b/SDK/Quantum.Sdk/Models/LoginPageBase.cs deleted file mode 100644 index a7f4ff7..0000000 --- a/SDK/Quantum.Sdk/Models/LoginPageBase.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.WebUtilities; -using Quantum.Infrastructure.Abstractions; - -namespace Quantum.Infrastructure.Models; - -public abstract class LoginPageBase : ComponentBase -{ - [Inject] protected NavigationManager NavigationManager { get; set; } = default!; - - protected string? ReturnUrl { get; private set; } - protected abstract IAccountService AccountService { get; } - - protected override void OnInitialized() - { - // 如果已经登录,直接跳转到返回地址或首页 - if (AccountService.IsAuthenticated) - { - NavigateToReturnUrl(); - return; - } - - // 获取returnUrl参数 - var uri = new Uri(NavigationManager.Uri); - var queryParameters = QueryHelpers.ParseQuery(uri.Query); - ReturnUrl = queryParameters.TryGetValue("returnUrl", out var returnUrlValues) - ? Uri.UnescapeDataString(returnUrlValues.First() ?? string.Empty) - : null; - } - - protected void NavigateToReturnUrl() => NavigationManager.NavigateTo(ReturnUrl ?? "/"); -} diff --git a/SDK/Quantum.Sdk/Models/NavigationItem.cs b/SDK/Quantum.Sdk/Models/NavigationItem.cs deleted file mode 100644 index 6d334c4..0000000 --- a/SDK/Quantum.Sdk/Models/NavigationItem.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Quantum.Infrastructure.Models; - -public class NavigationItem -{ - /// - /// 菜单项的唯一标识 - /// - public string Key { get; init; } = Guid.NewGuid().ToString(); - - /// - /// 菜单项的显示标题 - /// - public required string Title { get; init; } - - /// - /// 菜单项的图标 - /// - public required string Icon { get; init; } - - /// - /// 菜单项的路由 - /// - public required string Route { get; init; } - - /// - /// 子菜单项 - /// - public IEnumerable Children { get; init; } = []; -} diff --git a/SDK/Quantum.Sdk/Models/PersistentStatefulService.cs b/SDK/Quantum.Sdk/Models/PersistentStatefulService.cs deleted file mode 100644 index 10ca7f7..0000000 --- a/SDK/Quantum.Sdk/Models/PersistentStatefulService.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Quantum.Infrastructure.Abstractions; -using Quantum.Infrastructure.Utilities; -using System.Text.Json; - -namespace Quantum.Infrastructure.Models; - -public abstract class PersistentStatefulService( - string stateFile, - Func> serializer, - Func> deserializer) - : StatefulService, IPersistentService -{ - protected PersistentStatefulService(string stateFile) - : this(stateFile, - serializer: state => Task.Run(() => JsonSerializer.Serialize(state)), - deserializer: data => Task.Run(() => JsonSerializer.Deserialize(data))) - { } - - public virtual async Task LoadStateAsync() - { - if (!File.Exists(stateFile)) - { - State = default; - return; - } - try - { - var data = await File.ReadAllTextAsync(stateFile); - State = await deserializer(data.Decrypt()); - } - catch (Exception) - { - State = default; - } - } - - public virtual async Task SaveStateAsync() - { - if (State is null) - { - if (File.Exists(stateFile)) - { - File.Delete(stateFile); - } - return; - } - - var directory = Path.GetDirectoryName(stateFile); - if (!string.IsNullOrEmpty(directory)) - { - Directory.CreateDirectory(directory); - } - - var data = await serializer(State); - await File.WriteAllTextAsync(stateFile, data.Encrypt()); - } -} \ No newline at end of file diff --git a/SDK/Quantum.Sdk/Models/StatefulService.cs b/SDK/Quantum.Sdk/Models/StatefulService.cs deleted file mode 100644 index 3b6c47d..0000000 --- a/SDK/Quantum.Sdk/Models/StatefulService.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Quantum.Infrastructure.Abstractions; - -namespace Quantum.Infrastructure.Models; - -public abstract class StatefulService : IStatefulService -{ - private T? _state; - public virtual T? State - { - get => _state; - set - { - _state = value; - NotifyStateChanged(); - } - } - - public event Action? OnStateChanged = delegate { }; - - protected virtual void NotifyStateChanged() => OnStateChanged?.Invoke(); -} \ No newline at end of file diff --git a/SDK/Quantum.Sdk/Quantum.Sdk.csproj b/SDK/Quantum.Sdk/Quantum.Sdk.csproj index 8789021..909c51a 100644 --- a/SDK/Quantum.Sdk/Quantum.Sdk.csproj +++ b/SDK/Quantum.Sdk/Quantum.Sdk.csproj @@ -7,6 +7,13 @@ enable + + + + + + + diff --git a/SDK/Quantum.Sdk/Services/IInjectedCodeManager.cs b/SDK/Quantum.Sdk/Services/IInjectedCodeManager.cs new file mode 100644 index 0000000..dd698cd --- /dev/null +++ b/SDK/Quantum.Sdk/Services/IInjectedCodeManager.cs @@ -0,0 +1,25 @@ +namespace Quantum.Infrastructure.Models; + +/// +/// 管理注入到App.razor的代码片段 +/// +public interface IInjectedCodeManager +{ + /// + /// 添加代码到head部分 + /// + /// HTML代码 + public void AddToHead(string code); + + /// + /// 添加代码到blazor.web.js之前 + /// + /// HTML代码 + public void AddPreBlazor(string code); + + /// + /// 添加代码到blazor.web.js之后 + /// + /// HTML代码 + public void AddPostBlazor(string code); +} diff --git a/SDK/Quantum.Sdk/Services/IModuleManager.cs b/SDK/Quantum.Sdk/Services/IModuleManager.cs new file mode 100644 index 0000000..53835a2 --- /dev/null +++ b/SDK/Quantum.Sdk/Services/IModuleManager.cs @@ -0,0 +1,18 @@ +using Quantum.Sdk.Utilities; + +namespace Quantum.Sdk.Services; + +/// +/// 模块管理器接口 +/// +public interface IModuleManager +{ + /// + /// 获取指定ID和版本范围的模块 + /// + /// 模块ID + /// 最小版本要求,可选 + /// 最大版本要求,可选 + /// 包含模块实例的结果对象 + public Result GetModule(string moduleId, Version? minVersion = null, Version? maxVersion = null); +} diff --git a/SDK/Quantum.Sdk/Services/IServiceManager.cs b/SDK/Quantum.Sdk/Services/IServiceManager.cs new file mode 100644 index 0000000..e79ef14 --- /dev/null +++ b/SDK/Quantum.Sdk/Services/IServiceManager.cs @@ -0,0 +1,16 @@ +using Quantum.Infrastructure.Abstractions; + +namespace Quantum.Sdk.Services; + +/// +/// 服务管理器接口 +/// +public interface IServiceManager +{ + /// + /// 添加一个急切初始化的服务 + /// + /// 要添加的可初始化服务实例 + /// 服务管理器实例,用于链式调用 + IServiceManager AddEagerInitializeService(IInitializableService service); +} diff --git a/SDK/Quantum.Sdk/Utilities/Assertion.cs b/SDK/Quantum.Sdk/Utilities/Assertion.cs index 0b3c0fa..965f9bb 100644 --- a/SDK/Quantum.Sdk/Utilities/Assertion.cs +++ b/SDK/Quantum.Sdk/Utilities/Assertion.cs @@ -1,7 +1,18 @@ -namespace Quantum.Infrastructure.Utilities; +namespace Quantum.Sdk.Utilities; +/// +/// 断言工具类,用于参数验证 +/// public static class Assertion { + /// + /// 验证对象不为null + /// + /// 对象类型 + /// 要验证的值 + /// 参数名称,可选 + /// 非null的值 + /// 当值为null时抛出 public static T NotNull(T? value, string? paramName = null) where T : class { if (value is null) @@ -11,6 +22,13 @@ public static T NotNull(T? value, string? paramName = null) where T : class return value; } + /// + /// 验证字符串不为null且不为空 + /// + /// 要验证的字符串 + /// 参数名称,可选 + /// 非null且非空的字符串 + /// 当字符串为null或空时抛出 public static string NotNullNorEmpty(string? value, string? paramName = null) { if (string.IsNullOrEmpty(value)) @@ -19,4 +37,4 @@ public static string NotNullNorEmpty(string? value, string? paramName = null) } return value; } -} \ No newline at end of file +} diff --git a/SDK/Quantum.Sdk/Utilities/CachedEntity.cs b/SDK/Quantum.Sdk/Utilities/CachedEntity.cs index bdde45c..a171f83 100644 --- a/SDK/Quantum.Sdk/Utilities/CachedEntity.cs +++ b/SDK/Quantum.Sdk/Utilities/CachedEntity.cs @@ -1,15 +1,41 @@ -namespace Quantum.Infrastructure.Utilities; +namespace Quantum.Sdk.Utilities; +/// +/// 缓存实体类,用于管理需要异步更新的值 +/// +/// 缓存值的类型 +/// 用于更新值的异步函数 +/// 初始值 public class CachedEntity(Func>> updateFunc, T initValue) { + /// + /// 创建一个不需要更新的缓存实体 + /// + /// 初始值 public CachedEntity(T initValue) : this(() => Task.FromResult>(Result.Failure("No need to update.")), initValue) { } + /// + /// 获取或设置缓存的值 + /// public T Value { get; set; } = initValue; + /// + /// 获取实体是否已初始化 + /// public bool IsInitialized { get; private set; } + /// + /// 获取更新任务 + /// public Task UpdateTask { get; private set; } = Task.CompletedTask; + /// + /// 当值更新时触发的事件 + /// public event EventHandler? OnUpdated; + /// + /// 检查并触发值的更新 + /// + /// 当前实体实例,用于链式调用 public CachedEntity Peek() { if (UpdateTask.IsCompleted) @@ -18,6 +44,9 @@ public CachedEntity Peek() return this; } + /// + /// 异步更新值 + /// private async Task UpdateAsync() { var newValueResult = await updateFunc(); diff --git a/SDK/Quantum.Sdk/Utilities/Encryption.cs b/SDK/Quantum.Sdk/Utilities/Encryption.cs index 049bccd..0511f87 100644 --- a/SDK/Quantum.Sdk/Utilities/Encryption.cs +++ b/SDK/Quantum.Sdk/Utilities/Encryption.cs @@ -1,10 +1,16 @@ using System.Security.Cryptography; using System.Text; -namespace Quantum.Infrastructure.Utilities; +namespace Quantum.Sdk.Utilities; +/// +/// 加密工具类,提供基于设备特定密钥的AES加密和解密功能 +/// public static class Encryption { + /// + /// 设备特定的密钥和初始化向量,基于设备名称生成 + /// private static readonly Lazy<(byte[] Key, byte[] IV)> DeviceSpecificKeyIv = new(() => { var deviceId = Environment.MachineName; @@ -17,7 +23,12 @@ public static class Encryption return (deriveBytes.GetBytes(32), deriveBytes.GetBytes(16)); }); - public static string Encrypt(this string plainText) + /// + /// 使用AES算法加密字符串 + /// + /// 要加密的明文 + /// Base64编码的密文 + public static string Encrypt(string plainText) { using var aes = Aes.Create(); aes.Key = DeviceSpecificKeyIv.Value.Key; @@ -29,7 +40,12 @@ public static string Encrypt(this string plainText) return Convert.ToBase64String(cipherBytes); } - public static string Decrypt(this string cipherText) + /// + /// 使用AES算法解密字符串 + /// + /// 要解密的Base64编码密文 + /// 解密后的明文 + public static string Decrypt(string cipherText) { using var aes = Aes.Create(); aes.Key = DeviceSpecificKeyIv.Value.Key; diff --git a/SDK/Quantum.Sdk/Utilities/FileUtils.cs b/SDK/Quantum.Sdk/Utilities/FileUtils.cs index 56fb7cf..1d29f9f 100644 --- a/SDK/Quantum.Sdk/Utilities/FileUtils.cs +++ b/SDK/Quantum.Sdk/Utilities/FileUtils.cs @@ -1,7 +1,20 @@ -namespace Quantum.Infrastructure.Utilities; +namespace Quantum.Sdk.Utilities; +/// +/// 文件工具类,提供文件路径相关的工具方法 +/// public static class FileUtils { + /// + /// 数据文件的根目录路径 + /// public static readonly string DataFileFolder = Path.Combine(".", "data"); - public static string ToDataPath(this string filename) => Path.Combine(DataFileFolder, filename); + + /// + /// 获取模块数据文件的完整路径 + /// + /// 模块名称 + /// 文件名 + /// 完整的文件路径 + public static string GetDataFilePath(string moduleName, string filename) => Path.Combine(DataFileFolder, moduleName, filename); } diff --git a/SDK/Quantum.Sdk/Utilities/RequestClient.cs b/SDK/Quantum.Sdk/Utilities/RequestClient.cs index 8451398..bb1b8ab 100644 --- a/SDK/Quantum.Sdk/Utilities/RequestClient.cs +++ b/SDK/Quantum.Sdk/Utilities/RequestClient.cs @@ -1,19 +1,49 @@ using System.Net; -namespace Quantum.Infrastructure.Utilities; +namespace Quantum.Sdk.Utilities; +/// +/// HTTP请求选项类,用于配置RequestClient的行为 +/// public class RequestOptions { + /// + /// 获取或设置请求的Cookie列表 + /// public List? Cookies { get; set; } + + /// + /// 获取或设置请求超时时间(秒),默认为100秒 + /// public int TimeoutSeconds { get; set; } = 100; + + /// + /// 获取或设置是否允许自动重定向,默认为false + /// public bool AllowRedirects { get; set; } = false; + + /// + /// 获取或设置请求头字典 + /// public Dictionary? Headers { get; set; } } +/// +/// HTTP请求客户端类,继承自HttpClient,提供更多的配置选项和Cookie管理功能 +/// public class RequestClient : HttpClient { + /// + /// 获取Cookie容器实例 + /// public CookieContainer CookieContainer { get; } + /// + /// 创建RequestClient的私有构造函数 + /// + /// HTTP消息处理器 + /// Cookie容器 + /// 请求选项 private RequestClient(HttpMessageHandler handler, CookieContainer cookieContainer, RequestOptions? options = null) : base(handler) { CookieContainer = cookieContainer; @@ -39,6 +69,11 @@ private RequestClient(HttpMessageHandler handler, CookieContainer cookieContaine } } + /// + /// 创建RequestClient实例的工厂方法 + /// + /// 请求选项 + /// 新的RequestClient实例 public static RequestClient Create(RequestOptions? options = null) { var cookieContainer = new CookieContainer(); @@ -52,4 +87,4 @@ public static RequestClient Create(RequestOptions? options = null) return new RequestClient(handler, cookieContainer, options); } -} \ No newline at end of file +} diff --git a/SDK/Quantum.Sdk/Utilities/Result.cs b/SDK/Quantum.Sdk/Utilities/Result.cs index 740053d..8dd46bc 100644 --- a/SDK/Quantum.Sdk/Utilities/Result.cs +++ b/SDK/Quantum.Sdk/Utilities/Result.cs @@ -1,15 +1,56 @@ -namespace Quantum.Infrastructure.Utilities; +namespace Quantum.Sdk.Utilities; +/// +/// 表示一个操作的结果,包含成功状态、值和消息 +/// +/// 操作是否成功 +/// 结果值 +/// 结果消息 public record Result(bool IsSuccess, object? Value, string Message) { + /// + /// 创建一个成功的结果 + /// + /// 可选的成功消息 + /// 成功的结果实例 public static Result Success(string message = "") => new(true, default, message); + + /// + /// 创建一个带有指定值的成功结果 + /// + /// 结果值的类型 + /// 结果值 + /// 可选的成功消息 + /// 成功的泛型结果实例 public static Result Success(T value, string message = "") => new(true, value, message); + + /// + /// 创建一个失败的结果 + /// + /// 失败消息 + /// 失败的结果实例 public static Result Failure(string message) => new(false, default, message); } +/// +/// 表示一个带有类型T的操作结果 +/// +/// 结果值的类型 +/// 操作是否成功 +/// 类型为T的结果值 +/// 结果消息 public record Result(bool IsSuccess, T? Value, string Message) { + /// + /// 将泛型结果转换为非泛型结果 + /// + /// 要转换的泛型结果 public static implicit operator Result(Result result) => new(result.IsSuccess, result.Value, result.Message); + + /// + /// 将非泛型结果转换为泛型结果 + /// + /// 要转换的非泛型结果 public static implicit operator Result(Result result) { if (!result.IsSuccess) diff --git a/SDK/Quantum.Sdk/Utilities/TimeSlot.cs b/SDK/Quantum.Sdk/Utilities/TimeSlot.cs deleted file mode 100644 index bfebe0c..0000000 --- a/SDK/Quantum.Sdk/Utilities/TimeSlot.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Quantum.Infrastructure.Utilities; -public record TimeSlot(DateTime Date, TimeOnly StartTime, TimeOnly EndTime) -{ - public static TimeSlot Parse(string timeSlotStr) - { - // 解析类似 "2025年06月21日(14:00-16:00)" 的格式 - var datePart = timeSlotStr[..11]; // 2025年06月21日 - var timePart = timeSlotStr[12..^1]; // 14:00-16:00 - - var year = int.Parse(datePart[..4]); - var month = int.Parse(datePart[5..7]); - var day = int.Parse(datePart[8..10]); - - var times = timePart.Split('-'); - var startTime = TimeOnly.Parse(times[0]); - var endTime = TimeOnly.Parse(times[1]); - - return new TimeSlot( - new DateTime(year, month, day), - startTime, - endTime - ); - } - - public override string ToString() => $"{Date:yyyy年MM月dd日}({StartTime:HH:mm}-{EndTime:HH:mm})"; - - public bool OverlapsWith(TimeSlot other) => Date == other.Date && StartTime < other.EndTime && other.StartTime < EndTime; -}