From 4180140ae474ef21e590342d2bf0f7537a862e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E5=85=86=E5=B0=89?= <228127304@qq.com> Date: Wed, 26 Aug 2026 17:47:15 +0800 Subject: [PATCH] =?UTF-8?q?Signal=20=E6=95=B0=E6=8D=AE=E4=B8=AD=E5=BF=83?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E8=BF=90=E8=A1=8C=E6=97=B6=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=20schema=EF=BC=8C=E8=A7=A3=E9=99=A4=20StandardScene=20?= =?UTF-8?q?=E5=B7=A5=E7=A8=8B=E7=BC=96=E8=AF=91=E8=80=A6=E5=90=88=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 加固 Config/Signal 路径解析,前端 DataCenter 表单类型与 API 对齐。 Co-authored-by: Cursor --- MiGu.Server/Signal/SignalDataStore.cs | 39 +++++- .../Signal/SignalModelSchemaResolver.cs | 112 ++++++++++-------- .../Signal/SignalTableManifestLoader.cs | 8 +- .../simple-platform-vue/src/api/signalData.ts | 2 +- .../src/views/admin/DataCenterView.vue | 4 +- 5 files changed, 103 insertions(+), 62 deletions(-) diff --git a/MiGu.Server/Signal/SignalDataStore.cs b/MiGu.Server/Signal/SignalDataStore.cs index 230b56a..59e19f9 100644 --- a/MiGu.Server/Signal/SignalDataStore.cs +++ b/MiGu.Server/Signal/SignalDataStore.cs @@ -15,8 +15,8 @@ public sealed record SignalTableDef( IReadOnlyList Columns); /// -/// 读写 SimpleLite 工作目录 Config/Signal/*.json,供迷毂「数据中心」表格编辑。 -/// 表结构优先从 StandardScene.Signal.dll 反射;无插件时才读 signal-tables.json。 +/// 读写 Simple3 工作目录 Config/Signal/*.json,供迷毂「数据中心」表格编辑。 +/// 表结构只从 Simple3 plugins/StandardScene.Signal.dll 反射;无插件时才读 signal-tables.json。 /// public sealed class SignalDataStore { @@ -28,11 +28,11 @@ public sealed class SignalDataStore private static readonly object FileLock = new(); - private readonly SimpleLiteLauncher _launcher; + private readonly Simple3Launcher _launcher; private IReadOnlyList? _cachedTables; private long _cachedSignature; - public SignalDataStore(SimpleLiteLauncher launcher) => _launcher = launcher; + public SignalDataStore(Simple3Launcher launcher) => _launcher = launcher; public IReadOnlyList GetTables() { @@ -55,9 +55,11 @@ public sealed class SignalDataStore { var wd = _launcher.ResolveWorkingDirectory(); if (string.IsNullOrWhiteSpace(wd)) - return (null, "未找到 SimpleLite 工作目录,无法定位 Config/Signal"); + return (null, "未找到 Simple3 工作目录,无法定位 Config/Signal"); + + if (!TryResolveUnderSignalDir(wd, table.FileName, out var dest, out var pathError)) + return (null, pathError); - var dest = Path.GetFullPath(Path.Combine(wd, "Config", "Signal", table.FileName)); if (File.Exists(dest)) return (dest, null); @@ -109,4 +111,29 @@ public sealed class SignalDataStore lock (FileLock) File.WriteAllText(path, rows.ToJsonString(FileJson)); } + + private static bool TryResolveUnderSignalDir(string workingDirectory, string fileName, out string dest, out string error) + { + dest = ""; + error = ""; + if (string.IsNullOrWhiteSpace(fileName) || + Path.IsPathRooted(fileName) || + fileName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) + { + error = "信号表文件名非法"; + return false; + } + + var root = Path.GetFullPath(Path.Combine(workingDirectory, "Config", "Signal")); + dest = Path.GetFullPath(Path.Combine(root, fileName)); + var prefix = root.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; + if (!dest.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + error = "信号表路径超出 Config/Signal"; + dest = ""; + return false; + } + + return true; + } } diff --git a/MiGu.Server/Signal/SignalModelSchemaResolver.cs b/MiGu.Server/Signal/SignalModelSchemaResolver.cs index e651f59..90450b8 100644 --- a/MiGu.Server/Signal/SignalModelSchemaResolver.cs +++ b/MiGu.Server/Signal/SignalModelSchemaResolver.cs @@ -1,13 +1,12 @@ using System.ComponentModel; using System.Reflection; using System.Text.Json.Serialization; -using StandardScene.Signal.Model; namespace MiGu.Server.Signal; /// -/// 从 StandardScene.Signal 程序集反射数据中心表和列。 -/// 优先使用 MiGu.Server 编译期引用的程序集;否则再从 SimpleLite plugins 加载。 +/// 从 Simple3 plugins/StandardScene.Signal.dll 反射数据中心表和列。 +/// 平台不编译引用该插件工程,避免仓外路径和 Windows TFM 耦合。 /// public static class SignalModelSchemaResolver { @@ -24,18 +23,19 @@ public static class SignalModelSchemaResolver public static IReadOnlyList ResolveTables(string? pluginsDir, string? workingDirectory = null) { - var assembly = TryGetSignalAssembly(pluginsDir, workingDirectory); - if (assembly == null) - return Array.Empty(); - - try + return WithSignalAssembly(pluginsDir, workingDirectory, assembly => { - return BuildTablesFromAssembly(assembly); - } - catch - { - return Array.Empty(); - } + if (assembly == null) + return Array.Empty(); + try + { + return BuildTablesFromAssembly(assembly); + } + catch + { + return Array.Empty(); + } + }); } public static IReadOnlyList ResolveColumns(string? modelName, string? pluginsDir, string? workingDirectory = null) @@ -43,61 +43,76 @@ public static class SignalModelSchemaResolver if (string.IsNullOrWhiteSpace(modelName)) return Array.Empty(); - var assembly = TryGetSignalAssembly(pluginsDir, workingDirectory); - if (assembly == null) - return Array.Empty(); - - try + return WithSignalAssembly(pluginsDir, workingDirectory, assembly => { - var type = assembly.GetType($"{ModelNamespace}.{modelName.Trim()}", throwOnError: false, ignoreCase: true); - if (type == null) + if (assembly == null) return Array.Empty(); - - return DiscoverColumns(type); - } - catch - { - return Array.Empty(); - } + try + { + var type = assembly.GetType($"{ModelNamespace}.{modelName.Trim()}", throwOnError: false, ignoreCase: true); + if (type == null) + return Array.Empty(); + return DiscoverColumns(type); + } + catch + { + return Array.Empty(); + } + }); } - private static Assembly? TryGetSignalAssembly(string? pluginsDir, string? workingDirectory) + private static T WithSignalAssembly(string? pluginsDir, string? workingDirectory, Func use) { - try - { - var referenced = typeof(PlcStationModel).Assembly; - if (HasModelTypes(referenced)) - return referenced; - } - catch - { - /* 未引用插件工程时继续走 LoadFrom */ - } - var assemblyPath = FindAssemblyPath(pluginsDir); if (assemblyPath == null) - return null; + return use(null); + + var expected = Path.GetFullPath(assemblyPath); + var cached = FindLoadedFrom(expected); + if (cached != null) + return use(cached); var probeDirs = BuildProbeDirs(pluginsDir, workingDirectory); - ResolveEventHandler? handler = null; - handler = (_, args) => ResolveAssembly(args.Name, probeDirs); + ResolveEventHandler handler = (_, args) => ResolveAssembly(args.Name, probeDirs); AppDomain.CurrentDomain.AssemblyResolve += handler; try { - var loaded = Assembly.LoadFrom(assemblyPath); - return HasModelTypes(loaded) ? loaded : null; + var loaded = Assembly.LoadFrom(expected); + return use(HasModelTypes(loaded) ? loaded : null); } catch { - return null; + return use(null); } finally { - if (handler != null) - AppDomain.CurrentDomain.AssemblyResolve -= handler; + AppDomain.CurrentDomain.AssemblyResolve -= handler; } } + private static Assembly? FindLoadedFrom(string expectedFullPath) + { + try + { + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + if (!string.Equals(assembly.GetName().Name, "StandardScene.Signal", StringComparison.OrdinalIgnoreCase)) + continue; + if (string.IsNullOrWhiteSpace(assembly.Location)) + continue; + if (!string.Equals(Path.GetFullPath(assembly.Location), expectedFullPath, StringComparison.OrdinalIgnoreCase)) + continue; + return HasModelTypes(assembly) ? assembly : null; + } + } + catch + { + /* 继续 LoadFrom */ + } + + return null; + } + private static bool HasModelTypes(Assembly assembly) { return SafeGetTypes(assembly).Any(t => @@ -119,7 +134,6 @@ public static class SignalModelSchemaResolver Add(workingDirectory); if (!string.IsNullOrWhiteSpace(pluginsDir)) Add(Path.GetDirectoryName(pluginsDir)); - Add(AppContext.BaseDirectory); return dirs; } diff --git a/MiGu.Server/Signal/SignalTableManifestLoader.cs b/MiGu.Server/Signal/SignalTableManifestLoader.cs index 016bcbd..a6cab74 100644 --- a/MiGu.Server/Signal/SignalTableManifestLoader.cs +++ b/MiGu.Server/Signal/SignalTableManifestLoader.cs @@ -16,7 +16,7 @@ public static class SignalTableManifestLoader PropertyNameCaseInsensitive = true }; - public static IReadOnlyList Load(SimpleLiteLauncher launcher) + public static IReadOnlyList Load(Simple3Launcher launcher) { var workingDirectory = launcher.ResolveWorkingDirectory(); var pluginsDir = launcher.ResolvePluginsDir(); @@ -31,7 +31,7 @@ public static class SignalTableManifestLoader .ToList(); } - public static long ComputeManifestSignature(SimpleLiteLauncher launcher) + public static long ComputeManifestSignature(Simple3Launcher launcher) { long sig = 0; foreach (var path in ResolveManifestPaths(launcher)) @@ -61,7 +61,7 @@ public static class SignalTableManifestLoader return sig; } - private static SignalTableManifest TryLoadManifest(SimpleLiteLauncher launcher) + private static SignalTableManifest TryLoadManifest(Simple3Launcher launcher) { foreach (var path in ResolveManifestPaths(launcher)) { @@ -80,7 +80,7 @@ public static class SignalTableManifestLoader ?? new SignalTableManifest(); } - private static IEnumerable ResolveManifestPaths(SimpleLiteLauncher launcher) + private static IEnumerable ResolveManifestPaths(Simple3Launcher launcher) { var wd = launcher.ResolveWorkingDirectory(); if (!string.IsNullOrWhiteSpace(wd)) diff --git a/frontends/apps/simple-platform-vue/src/api/signalData.ts b/frontends/apps/simple-platform-vue/src/api/signalData.ts index 2fd9cc4..6b6d26e 100644 --- a/frontends/apps/simple-platform-vue/src/api/signalData.ts +++ b/frontends/apps/simple-platform-vue/src/api/signalData.ts @@ -34,7 +34,7 @@ export async function listSignalTables(): Promise { if (MOCK) { return { signalEnabled: true, - workingDirectory: 'D:\\工作\\stand\\SimpleLite', + workingDirectory: 'D:\\工作\\stand\\Simple3', tables: [ { id: 'stations', title: 'PLC机构', category: 'PLC数据管理', fileName: 'stations.json' }, { id: 'docks', title: '机构工位', category: 'PLC数据管理', fileName: 'station-docks.json' }, diff --git a/frontends/apps/simple-platform-vue/src/views/admin/DataCenterView.vue b/frontends/apps/simple-platform-vue/src/views/admin/DataCenterView.vue index 1005edb..e73d80d 100644 --- a/frontends/apps/simple-platform-vue/src/views/admin/DataCenterView.vue +++ b/frontends/apps/simple-platform-vue/src/views/admin/DataCenterView.vue @@ -6,7 +6,7 @@

{{ table?.title ?? title }}

- 读写 SimpleLite 工作目录 Config/Signal;改完后到信号交互进程点重新加载配置。 + 读写 Simple3 工作目录 Config/Signal;改完后到信号交互进程点重新加载配置。 点位为 BOOL 时按「字节 + 位」直接读写。 ({{ table.fileName }}{{ table.exists ? '' : ' · 文件尚未创建,保存后写入' }}) @@ -117,7 +117,7 @@ const formGroups = computed(() => { const dialogVisible = ref(false) const editingIndex = ref(-1) -const form = reactive>({}) +const form = reactive>({}) function truthy(v: unknown): boolean { return v === true || v === 'true' || v === 1 || v === '1'