425 lines
15 KiB
C#
425 lines
15 KiB
C#
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|||
|
|
using CommunityToolkit.Mvvm.Messaging;
|
|||
|
|
using Cowain.Base.Abstractions.Navigation;
|
|||
|
|
using Cowain.Base.Abstractions.Plugin;
|
|||
|
|
using Cowain.Base.Attributes;
|
|||
|
|
using Cowain.Base.DBContext;
|
|||
|
|
using Cowain.Base.Helpers;
|
|||
|
|
using Cowain.Base.IServices;
|
|||
|
|
using Cowain.Base.Models;
|
|||
|
|
using Cowain.Base.Models.Menu;
|
|||
|
|
using Ke.Bee.Localization.Extensions;
|
|||
|
|
using Ke.Bee.Localization.Options;
|
|||
|
|
using Ke.Bee.Localization.Providers;
|
|||
|
|
using Microsoft.EntityFrameworkCore;
|
|||
|
|
using Microsoft.Extensions.Configuration;
|
|||
|
|
using Microsoft.Extensions.DependencyInjection;
|
|||
|
|
using Microsoft.Extensions.Hosting;
|
|||
|
|
using Microsoft.Extensions.Logging;
|
|||
|
|
using Microsoft.Extensions.Options;
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Data;
|
|||
|
|
using System.Globalization;
|
|||
|
|
using System.IO;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Reflection;
|
|||
|
|
using System.Text.Json;
|
|||
|
|
using System.Threading;
|
|||
|
|
|
|||
|
|
namespace Cowain.TestProject.Services;
|
|||
|
|
|
|||
|
|
public static class ConfigureServices
|
|||
|
|
{
|
|||
|
|
private static List<Assembly>? _assemblies;
|
|||
|
|
private static IOptions<AppSettings>? appSettings;
|
|||
|
|
private static MenuConfigurationContext? menuConfigurationContext;
|
|||
|
|
private static readonly List<IPlugin> _plugins = new();
|
|||
|
|
private static ILogger<App>? _logger;
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册服务
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
public static IServiceCollection AddServices(this IServiceCollection services, IConfiguration configuration, ILogger<App>? logger)
|
|||
|
|
{
|
|||
|
|
_logger = logger;
|
|||
|
|
// 注册全局配置
|
|||
|
|
services.AddSettings();
|
|||
|
|
_assemblies = GetAssemblies();
|
|||
|
|
GlobalData.Instance.AddOrUpdate("Assemblies", _assemblies);
|
|||
|
|
services.AddMemoryCache();
|
|||
|
|
//注入configuration
|
|||
|
|
services.AddSingleton(configuration);
|
|||
|
|
services.AddSingleton<IMessenger, WeakReferenceMessenger>();
|
|||
|
|
// 注册数据库
|
|||
|
|
services.AddDbContextService(configuration);
|
|||
|
|
// 注册视图模型
|
|||
|
|
services.AddViewModels();
|
|||
|
|
// 注册应用菜单
|
|||
|
|
services.AddMenus();
|
|||
|
|
// 注册视图导航器
|
|||
|
|
services.AddSingleton<IViewNavigator, DefaultViewNavigator>();
|
|||
|
|
// 注册TransientAttribute
|
|||
|
|
services.AddTransientAttributes();
|
|||
|
|
// 注册SingletonAttribute
|
|||
|
|
services.AddSingletonAttributes();
|
|||
|
|
// 注册所有数据库服务
|
|||
|
|
services.AddDbServices();
|
|||
|
|
// 注册所有实现IHostedService的类
|
|||
|
|
services.AddHostedServices();
|
|||
|
|
//注册插件服务
|
|||
|
|
services.AddPlugins();
|
|||
|
|
// 注册本地化
|
|||
|
|
services.AddLocalization();
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
private static List<Assembly> GetAssemblies()
|
|||
|
|
{
|
|||
|
|
string? pluginPath = appSettings?.Value.PluginPath;
|
|||
|
|
|
|||
|
|
// 获取当前已加载的程序集(使用FullName作为唯一标识)
|
|||
|
|
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies()
|
|||
|
|
.ToDictionary(a => a.GetName().FullName);
|
|||
|
|
|
|||
|
|
// 创建结果列表,初始化为已加载的程序集
|
|||
|
|
var assemblies = new List<Assembly>(loadedAssemblies.Values);
|
|||
|
|
|
|||
|
|
// 加载插件程序集
|
|||
|
|
if (Directory.Exists(pluginPath))
|
|||
|
|
{
|
|||
|
|
var pluginFiles = Directory.GetFiles(pluginPath, "Plugin.*.dll", SearchOption.AllDirectories);
|
|||
|
|
foreach (var file in pluginFiles)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 获取程序集名称而不加载它
|
|||
|
|
var assemblyName = AssemblyName.GetAssemblyName(file);
|
|||
|
|
|
|||
|
|
// 检查是否已加载同名程序集
|
|||
|
|
if (!loadedAssemblies.ContainsKey(assemblyName.FullName))
|
|||
|
|
{
|
|||
|
|
// 加载程序集并添加到结果列表
|
|||
|
|
var assembly = Assembly.LoadFrom(file);
|
|||
|
|
assemblies.Add(assembly);
|
|||
|
|
loadedAssemblies[assemblyName.FullName] = assembly;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
// 处理加载错误(例如,非程序集文件、版本冲突等)
|
|||
|
|
_logger?.LogError(ex, $"Error loading assembly from {file}: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return assemblies;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册所有viewmodel
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private static IServiceCollection AddViewModels(this IServiceCollection services)
|
|||
|
|
{
|
|||
|
|
if (_assemblies == null)
|
|||
|
|
{
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
var viewModelTypes = _assemblies.SelectMany(a => a.GetTypes())
|
|||
|
|
.Where(t => t.Name.EndsWith("ViewModel") && t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(ObservableObject)));
|
|||
|
|
foreach (var viewModelType in viewModelTypes)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
services.AddTransient(viewModelType);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
_logger?.LogError(ex, "ViewModel注入错误");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册全局配置
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private static IServiceCollection AddSettings(this IServiceCollection services)
|
|||
|
|
{
|
|||
|
|
appSettings = Options.Create(new AppSettings
|
|||
|
|
{
|
|||
|
|
OutputPath = Path.Combine(AppContext.BaseDirectory, "Output"),
|
|||
|
|
PluginPath = Path.Combine(AppContext.BaseDirectory, "Plugins")
|
|||
|
|
});
|
|||
|
|
services.AddSingleton(appSettings);
|
|||
|
|
GlobalData.Instance.AddOrUpdate("AppSettings", appSettings.Value);
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册数据库
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private static IServiceCollection AddDbContextService(this IServiceCollection services, IConfiguration configuration)
|
|||
|
|
{
|
|||
|
|
var database_config = configuration.GetSection("Database")["db"];
|
|||
|
|
if (string.IsNullOrEmpty(database_config))
|
|||
|
|
{
|
|||
|
|
_logger?.LogError("未配置数据库");
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
if (!Enum.TryParse<DataBaseType>(database_config.ToUpper(), out var cmdType))
|
|||
|
|
{
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
// 配置EF Core日志过滤(针对9.0.0版本)
|
|||
|
|
services.AddLogging(loggingBuilder =>
|
|||
|
|
{
|
|||
|
|
// 过滤EF Core的Info级别日志,只记录Warning及以上
|
|||
|
|
loggingBuilder.AddFilter(
|
|||
|
|
"Microsoft.EntityFrameworkCore",
|
|||
|
|
LogLevel.Warning // 最低日志级别设为Warning
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 专门过滤数据库命令日志(SQL执行语句等)
|
|||
|
|
loggingBuilder.AddFilter(
|
|||
|
|
"Microsoft.EntityFrameworkCore.Database.Command",
|
|||
|
|
LogLevel.Warning
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
services.AddPooledDbContextFactory<SqlDbContext>(options =>
|
|||
|
|
{
|
|||
|
|
switch (cmdType)
|
|||
|
|
{
|
|||
|
|
case DataBaseType.SQLITE:
|
|||
|
|
var SqlLite_connection = configuration.GetConnectionString("SqlLiteConn");
|
|||
|
|
options.UseSqlite(SqlLite_connection);
|
|||
|
|
break;
|
|||
|
|
case DataBaseType.SQLSERVER:
|
|||
|
|
var SqlServer_connection = configuration.GetConnectionString("SqlServerConn");
|
|||
|
|
options.UseSqlServer(SqlServer_connection);
|
|||
|
|
break;
|
|||
|
|
case DataBaseType.MYSQL:
|
|||
|
|
var MySql_connection = configuration.GetConnectionString("MySqlConn");
|
|||
|
|
options.UseMySql(MySql_connection, new MySqlServerVersion(new Version(8, 0, 26)));
|
|||
|
|
break;
|
|||
|
|
case DataBaseType.POSTGRES:
|
|||
|
|
var Postgres_connection = configuration.GetConnectionString("PostGresConn");
|
|||
|
|
options.UseNpgsql(Postgres_connection);
|
|||
|
|
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
|
|||
|
|
AppContext.SetSwitch("Npgsql.DisableDateTimeInfinityConversions", true);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#if DEBUG
|
|||
|
|
options.EnableSensitiveDataLogging();
|
|||
|
|
#endif
|
|||
|
|
}, 100);
|
|||
|
|
|
|||
|
|
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册应用菜单
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private static IServiceCollection AddMenus(this IServiceCollection services)
|
|||
|
|
{
|
|||
|
|
// 从配置文件读取菜单注入到 DI 容器
|
|||
|
|
var menuItems = JsonSerializer.Deserialize<List<MenuItem>>(
|
|||
|
|
File.ReadAllBytes(Path.Combine(AppContext.BaseDirectory, "Configs", "menus.json"))
|
|||
|
|
);
|
|||
|
|
menuConfigurationContext = new MenuConfigurationContext(menuItems);
|
|||
|
|
services.AddSingleton(menuConfigurationContext);
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册插件服务
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private static IServiceCollection AddPlugins(this IServiceCollection services)
|
|||
|
|
{
|
|||
|
|
if (_assemblies == null)
|
|||
|
|
{
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
var pluginTypes = _assemblies.SelectMany(a => a.GetTypes())
|
|||
|
|
.Where(t => typeof(PluginBase).IsAssignableFrom(t) && !t.IsAbstract);
|
|||
|
|
foreach (var type in pluginTypes)
|
|||
|
|
{
|
|||
|
|
if (Activator.CreateInstance(type) is IPlugin plugin)
|
|||
|
|
{
|
|||
|
|
plugin.RegisterServices(services, _assemblies);
|
|||
|
|
//只是注入插件,不需要配置菜单
|
|||
|
|
//plugin.ConfigureMenu(menuConfigurationContext);
|
|||
|
|
_plugins.Add(plugin);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
public static void InitializePlugins(this IServiceProvider serviceProvider)
|
|||
|
|
{
|
|||
|
|
foreach (var plugin in _plugins)
|
|||
|
|
{
|
|||
|
|
plugin.Initialize(serviceProvider);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
public static void ShutDownPlugins(this IServiceProvider serviceProvider)
|
|||
|
|
{
|
|||
|
|
foreach (var plugin in _plugins)
|
|||
|
|
{
|
|||
|
|
plugin.Shutdown();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册所有数据库服务
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private static IServiceCollection AddDbServices(this IServiceCollection services)
|
|||
|
|
{
|
|||
|
|
if (_assemblies == null)
|
|||
|
|
{
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
var types = _assemblies.SelectMany(a => a.GetTypes())
|
|||
|
|
.Where(t => typeof(IBaseService).IsAssignableFrom(t) && t != typeof(IBaseService));
|
|||
|
|
var implementTypes = types.Where(x => x.IsClass).ToArray();
|
|||
|
|
var interfaceTypes = types.Where(x => x.IsInterface).ToArray();
|
|||
|
|
foreach (var implementType in implementTypes)
|
|||
|
|
{
|
|||
|
|
var interfaceType = interfaceTypes.FirstOrDefault(x => x.IsAssignableFrom(implementType));
|
|||
|
|
if (interfaceType != null)
|
|||
|
|
services.AddScoped(interfaceType, implementType);
|
|||
|
|
}
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册所有TransientAttribute
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private static IServiceCollection AddTransientAttributes(this IServiceCollection services)
|
|||
|
|
{
|
|||
|
|
if (_assemblies == null)
|
|||
|
|
{
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
var transientTypes = _assemblies.SelectMany(a => a.GetTypes())
|
|||
|
|
.Where(t => t.GetCustomAttributes(typeof(TransientAttribute), true).Any() && t.IsClass && !t.IsAbstract);
|
|||
|
|
foreach (var transientType in transientTypes)
|
|||
|
|
{
|
|||
|
|
services.AddTransient(transientType);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册所有SingletonAttribute
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private static IServiceCollection AddSingletonAttributes(this IServiceCollection services)
|
|||
|
|
{
|
|||
|
|
if (_assemblies == null)
|
|||
|
|
{
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
var transientTypes = _assemblies.SelectMany(a => a.GetTypes())
|
|||
|
|
.Where(t => t.GetCustomAttributes(typeof(SingletonAttribute), true).Any() && t.IsClass && !t.IsAbstract);
|
|||
|
|
foreach (var transientType in transientTypes)
|
|||
|
|
{
|
|||
|
|
services.AddSingleton(transientType);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册所有实现IHostedService的类
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private static IServiceCollection AddHostedServices(this IServiceCollection services)
|
|||
|
|
{
|
|||
|
|
if (_assemblies == null)
|
|||
|
|
{
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var hostedServiceTypes = _assemblies.SelectMany(a => a.GetTypes())
|
|||
|
|
.Where(t => typeof(IHostedService).IsAssignableFrom(t) && t.IsClass && !t.IsAbstract);
|
|||
|
|
|
|||
|
|
// 获取 AddHostedService 方法的泛型定义
|
|||
|
|
var addHostedServiceMethod = typeof(ServiceCollectionHostedServiceExtensions)
|
|||
|
|
.GetMethods()
|
|||
|
|
.First(m => m.Name == "AddHostedService" && m.IsGenericMethod && m.GetParameters().Length == 1);
|
|||
|
|
|
|||
|
|
foreach (var hostedServiceType in hostedServiceTypes)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 构造泛型方法并调用
|
|||
|
|
var genericMethod = addHostedServiceMethod.MakeGenericMethod(hostedServiceType);
|
|||
|
|
genericMethod.Invoke(null, new object[] { services });
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
_logger?.LogError(ex, $"注册后台服务 {hostedServiceType.FullName} 失败");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return services;
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 注册本地化
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="services"></param>
|
|||
|
|
/// <returns></returns>
|
|||
|
|
private static IServiceCollection AddLocalization(this IServiceCollection services)
|
|||
|
|
{
|
|||
|
|
services.AddLocalization<AvaloniaJsonLocalizationProvider>(() =>
|
|||
|
|
{
|
|||
|
|
var options = new AvaloniaLocalizationOptions(
|
|||
|
|
// 支持的本地化语言文化
|
|||
|
|
[
|
|||
|
|
new("en-US"),
|
|||
|
|
new("zh-CN")
|
|||
|
|
],
|
|||
|
|
// defaultCulture, 用于设置当前文化(currentCulture)不在 cultures 列表中时的情况以及作为缺失的本地化条目的备用文化(fallback culture)
|
|||
|
|
new CultureInfo("en-US"),
|
|||
|
|
// currentCulture 在基础设施加载时设置,可以从应用程序设置或其他地方获取
|
|||
|
|
Thread.CurrentThread.CurrentCulture,
|
|||
|
|
// 包含本地化 JSON 文件的资源路径
|
|||
|
|
$"{typeof(App).Namespace}/Assets/i18n");
|
|||
|
|
return options;
|
|||
|
|
});
|
|||
|
|
return services;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
}
|