修改 CloudProduct ImageCache 项目改为 插件

This commit is contained in:
chfei committed 2026-10-22 15:56:55 +08:00
1 parent 5ff52dea92
commit 292cb8638c
26 files changed
+417 -282

No files matched your search

@@ -0,0 +1,53 @@
using MAX.Plugins;
using Microsoft.Extensions.Hosting;
internal static class ConfigurationChecks
{
public static void Run()
{
var tempRoot = Path.GetFullPath(Path.GetTempPath());
var directory = Path.GetFullPath(Path.Combine(tempRoot, "StorePluginChecks-" + Guid.NewGuid().ToString("N")));
Directory.CreateDirectory(directory);
try
{
var environment = Host.CreateApplicationBuilder().Environment;
environment.EnvironmentName = "PluginTest";
var first = Path.Combine(directory, "First");
var second = Path.Combine(directory, "Second");
Directory.CreateDirectory(first);
Directory.CreateDirectory(second);
File.WriteAllText(Path.Combine(first, "appsettings.json"), """{"Shared":"first","Required":"present"}""");
File.WriteAllText(Path.Combine(first, "appsettings.PluginTest.json"), """{"Shared":"environment"}""");
File.WriteAllText(Path.Combine(second, "appsettings.json"), """{"Shared":"second"}""");
using var a = new PluginContext("First", first, environment);
using var b = new PluginContext("Second", second, environment);
if (a.Configuration["Shared"] != "environment" || b.Configuration["Shared"] != "second")
throw new Exception("Configuration isolation/environment override failed");
if (a.RequireConfiguration("Required") != "present") throw new Exception("Required setting missing");
try { b.RequireConfiguration("Required"); throw new Exception("Expected missing key failure"); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Second") && ex.Message.Contains("Required")) { }
const string envKey = "Plugins__First__Shared";
var previous = System.Environment.GetEnvironmentVariable(envKey);
try
{
System.Environment.SetEnvironmentVariable(envKey, "specific-environment-variable");
using var overridden = new PluginContext("First", first, environment);
using var untouched = new PluginContext("Second", second, environment);
if (overridden.Configuration["Shared"] != "specific-environment-variable" || untouched.Configuration["Shared"] != "second")
throw new Exception("Plugin environment variable isolation failed");
}
finally { System.Environment.SetEnvironmentVariable(envKey, previous); }
var absent = Path.Combine(directory, "Absent");
Directory.CreateDirectory(absent);
try { using var invalid = new PluginContext("Absent", absent, environment); throw new Exception("Expected missing file failure"); }
catch (FileNotFoundException) { }
Console.WriteLine("PASS configuration isolation, environment overrides, missing file/key validation");
}
finally
{
if (!directory.StartsWith(tempRoot.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("Unexpected fixture path");
Directory.Delete(directory, recursive: true);
}
}
}
+28 -6
View File
@@ -1,3 +1,4 @@
using MAX.Plugins;
using System.Reflection;
using MAX.Framework.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
@@ -5,6 +6,8 @@ using Microsoft.Extensions.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.AspNetCore.Mvc.Controllers;
if (args.ElementAtOrDefault(0) == "configuration") { ConfigurationChecks.Run(); return; }
// Run each scenario in a fresh process: default ALC intentionally cannot unload plugins.
var finderType = typeof(ServiceCollectionExtensions).Assembly.GetType("MAX.Framework.DependencyInjection.StoreAppTypeFinder", true)!;
var finder = finderType.GetProperty("Current")!.GetValue(null);
@@ -36,17 +39,36 @@ var feature = new ControllerFeature();
mvc.PartManager.PopulateFeature(feature);
if (!feature.Controllers.Any(t => t.Name == "ImageCacheController")) throw new Exception("ImageCache controller missing");
if (mode != "image" && !feature.Controllers.Any(t => t.Name == "GoodsController")) throw new Exception("Goods controller missing");
if (mode == "image")
if (mode is "image" or "plugins")
{
var host = Host.CreateApplicationBuilder();
var core = typeof(ServiceCollectionExtensions).GetMethod("AddStoreCore", BindingFlags.Static | BindingFlags.NonPublic)!;
host.Configuration["Search:Url"] = "https://host.invalid";
host.Configuration["ImageCache:ConnectionString"] = "host-only";
core.Invoke(null, new object[] { host, plugins, new Dictionary<Type, object>() });
if (string.IsNullOrEmpty(host.Configuration["ImageCache:ConnectionString"])) throw new Exception("Plugin configuration missing");
using var built = host.Build();
var image = built.Services.GetRequiredKeyedService<PluginContext>("ImageCache");
if (image.Configuration["ImageCache:ConnectionString"] is null or "host-only") throw new Exception("Image config isolation failed");
if (image.Configuration["Search:Url"] != null) throw new Exception("Host settings leaked into ImageCache");
if (host.Configuration["ImageCache:ConnectionString"] != "host-only") throw new Exception("Plugin modified host configuration");
if (mode == "plugins")
{
var cloud = built.Services.GetRequiredKeyedService<PluginContext>("CloudProduct");
if (cloud.Configuration["Search:Url"] is null or "https://host.invalid") throw new Exception("Cloud config isolation failed");
if (host.Configuration["Search:Url"] != "https://host.invalid") throw new Exception("Cloud modified host configuration");
foreach (var name in new[] { "OpenSearchService", "UserFavoritesIndexService", "RawDataStorage", "GoodsIndexJob", "GoodsService" })
{
var type = plugins.SelectMany(p => p.GetTypes()).Single(t => t.Name == name);
using var scope = built.Services.CreateScope();
scope.ServiceProvider.GetRequiredService(type);
}
}
if (!host.Services.Any(d => d.ServiceType.Name == "ImageCacheService")) throw new Exception("Plugin DI registration missing");
if (!host.Services.Any(d => d.ServiceKey is string key && key == "ImageCacheConnection")) throw new Exception("Startup registration missing");
var registeredMvc = host.Services.AddControllers();
if (!host.Services.Any(d => d.ServiceKey is string key && key == "ImageCacheConnection")) throw new Exception("Plugin database registration missing");
var registeredMvc = built.Services.GetRequiredService<ApplicationPartManager>();
var registered = new ControllerFeature();
registeredMvc.PartManager.PopulateFeature(registered);
registeredMvc.PopulateFeature(registered);
if (!registered.Controllers.Any(t => t.Name == "ImageCacheController")) throw new Exception("Core MVC registration missing");
}
Console.WriteLine($"PASS plugins={plugins.Length}, controllers={feature.Controllers.Count}; dependencies/types loaded" + (mode == "image" ? "; configuration/DI/Startup/MVC registered" : ""));
Console.WriteLine($"PASS plugins={plugins.Length}, controllers={feature.Controllers.Count}; dependencies/types loaded" + (mode is "image" or "plugins" ? "; isolated configuration/DI/plugin lifecycle/MVC registered" : ""));