把一些项目移动到插件中

This commit is contained in:
chfei committed 2026-10-22 15:12:56 +08:00
1 parent d0f9f00b76
commit 5ff52dea92
60 files changed
+410 -27

No files matched your search

@@ -36,7 +36,7 @@ namespace MAX.Framework.DependencyInjection
host.AddLogger();
//host.Services.AddSingleton<ILoggerFacade>(NLogger.Instance);//注册log
IEnumerable<Assembly> assemblies = GetApplicationAssemblies();
IEnumerable<Assembly> assemblies = StoreAppTypeFinder.Current.GetAssemblies(host.Configuration["Plugins:Path"]).ToArray();
// 同一个类可能同时实现 IStartup 与 IModule,这里缓存实例,保证两个阶段拿到的是同一个对象
Dictionary<Type, object> instances = [];
return AddStoreCore(host, assemblies, instances);
@@ -62,7 +62,7 @@ namespace MAX.Framework.DependencyInjection
host.Services.AddControllers(options =>
var mvc = host.Services.AddControllers(options =>
{
options.Filters.Add<GlobalExceptionFilter>();
options.Filters.Add<UnifyResultFilter>();
@@ -87,6 +87,10 @@ namespace MAX.Framework.DependencyInjection
};
});
foreach (var assembly in assemblies)
{
mvc.AddApplicationPart(assembly);
}
return host.Services;
}
@@ -154,7 +158,7 @@ namespace MAX.Framework.DependencyInjection
catch (Exception ex)
{
// 带上模块名,避免只看到一个没有上下文的异常
throw new InvalidOperationException($"模块 {module.GetType().FullName} 的 AddModule 执行失败。", ex);
throw new InvalidOperationException($"模块 {module.GetType().FullName} 的 AddModule 执行失败:{ex.Message}", ex);
}
}
}
@@ -1,18 +1,132 @@
using MAX.Core.Reflections;
using MAX.Core.Reflections;
using System.Reflection;
using System.Runtime.Loader;
namespace MAX.Framework.DependencyInjection
namespace MAX.Framework.DependencyInjection;
/// <summary>发现宿主程序集及独立发布的插件;入口过滤统一使用 Match。</summary>
internal sealed class StoreAppTypeFinder : AppTypeFinder
{
/// <summary>
/// 本应用的类型查找器:在 AppTypeFinder 默认前缀(MAX. / Nexus.)之外,
/// 增加 StoreHub、KJD 前缀,用于发现宿主和各业务模块的程序集。
/// </summary>
internal sealed class StoreAppTypeFinder : AppTypeFinder
{
public static StoreAppTypeFinder Current { get; } = new();
public static StoreAppTypeFinder Current { get; } = new();
private readonly Dictionary<string, AssemblyDependencyResolver> resolvers = new(StringComparer.OrdinalIgnoreCase);
private readonly object gate = new();
private bool registered;
protected override List<string> AvailablePrefix()
{
return ["Store"];
}
}
protected override List<string> AvailablePrefix() => ["Store"];
public override List<Assembly> GetAssemblies() => GetAssemblies(null);
public List<Assembly> GetAssemblies(string? pluginPath)
{
lock (gate)
{
var path = Path.GetFullPath(string.IsNullOrWhiteSpace(pluginPath) ? "Plugins" : pluginPath, AppContext.BaseDirectory);
if (!Directory.Exists(path))
return base.GetAssemblies();
// 每个独立发布目录由入口 .deps.json 标识,避免把共享框架副本当作插件。
var entries = Directory.GetFiles(path, "*.deps.json", SearchOption.AllDirectories)
.Select(file => file[..^".deps.json".Length] + ".dll")
.Where(file => Match(Path.GetFileName(file)))
.OrderBy(file => file, StringComparer.OrdinalIgnoreCase).ToArray();
foreach (var entry in entries)
{
try
{
if (!File.Exists(entry))
throw new FileNotFoundException("插件入口 DLL 不存在。", entry);
resolvers.TryAdd(entry, new AssemblyDependencyResolver(entry));
}
catch (Exception ex)
{
throw new InvalidOperationException($"无法初始化插件 {entry}。", ex);
}
}
if (!registered)
{
AssemblyLoadContext.Default.Resolving += Resolve;
AssemblyLoadContext.Default.ResolvingUnmanagedDll += ResolveNative;
registered = true;
}
var plugins = new List<Assembly>();
foreach (var entry in entries)
{
try
{
var assembly = Load(entry);
ValidateDependencies(assembly);
assembly.GetTypes(); // 插件类型加载失败必须在启动时暴露。
plugins.Add(assembly);
}
catch (Exception ex)
{
var detail = ex is ReflectionTypeLoadException types
? string.Join("; ", types.LoaderExceptions.Select(error => error?.Message)) : ex.Message;
throw new InvalidOperationException($"加载插件 {entry} 失败:{detail}", ex);
}
}
return base.GetAssemblies().Concat(plugins).DistinctBy(a => a.FullName).ToList();
}
}
private static Assembly Load(string path)
{
var name = AssemblyName.GetAssemblyName(path);
var existing = AssemblyLoadContext.Default.Assemblies.FirstOrDefault(a =>
string.Equals(a.GetName().Name, name.Name, StringComparison.OrdinalIgnoreCase));
if (existing is null)
return AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(path));
if (!string.Equals(existing.FullName, name.FullName, StringComparison.OrdinalIgnoreCase))
throw new FileLoadException($"程序集版本冲突:请求 {name.FullName},已加载 {existing.FullName}。", path);
return existing;
}
private void ValidateDependencies(Assembly assembly)
{
foreach (var reference in assembly.GetReferencedAssemblies())
{
// 宿主 .deps.json 优先;插件发布目录中的副本仍须满足相同身份。
var candidate = ResolvePath(reference);
AssemblyLoadContext.Default.LoadFromAssemblyName(reference);
if (candidate is not null)
{
Load(candidate);
// 不强制遍历第三方库所有可选功能依赖;插件类型所需依赖由 GetTypes 校验。
}
}
}
private string? ResolvePath(AssemblyName name)
{
string? result = null;
foreach (var (entry, resolver) in resolvers)
{
var candidate = resolver.ResolveAssemblyToPath(name);
if (candidate is null) continue;
var identity = AssemblyName.GetAssemblyName(candidate);
if (result is not null && !string.Equals(AssemblyName.GetAssemblyName(result).FullName, identity.FullName, StringComparison.OrdinalIgnoreCase))
throw new FileLoadException($"插件依赖冲突:{result} 与 {candidate}(插件 {entry})。");
result ??= candidate;
}
return result;
}
private Assembly? Resolve(AssemblyLoadContext context, AssemblyName name)
{
var path = ResolvePath(name);
return path is null ? null : Load(path);
}
private IntPtr ResolveNative(Assembly assembly, string name)
{
// 优先使用请求程序集所属插件目录,避免原生库跨插件误匹配。
foreach (var (entry, resolver) in resolvers.OrderByDescending(pair =>
assembly.Location.StartsWith(Path.GetDirectoryName(pair.Key)! + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
{
var path = resolver.ResolveUnmanagedDllToPath(name);
if (path is not null) return System.Runtime.InteropServices.NativeLibrary.Load(path);
}
return IntPtr.Zero;
}
}