加固 Config/Signal 路径解析,前端 DataCenter 表单类型与 API 对齐。 Co-authored-by: Cursor <cursoragent@cursor.com>
175 lines
5.9 KiB
C#
175 lines
5.9 KiB
C#
using System.Text.Json;
|
|
using MiGu.Server.Launcher;
|
|
|
|
namespace MiGu.Server.Signal;
|
|
|
|
/// <summary>
|
|
/// 优先从 plugins/StandardScene.Signal.dll 反射表和列(Model 上的 SignalTable / DisplayName)。
|
|
/// 没有插件 DLL 时才读 signal-tables.json 或内置清单。
|
|
/// </summary>
|
|
public static class SignalTableManifestLoader
|
|
{
|
|
private const string ManifestFileName = "signal-tables.json";
|
|
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
PropertyNameCaseInsensitive = true
|
|
};
|
|
|
|
public static IReadOnlyList<SignalTableDef> Load(Simple3Launcher launcher)
|
|
{
|
|
var workingDirectory = launcher.ResolveWorkingDirectory();
|
|
var pluginsDir = launcher.ResolvePluginsDir();
|
|
var fromPlugin = SignalModelSchemaResolver.ResolveTables(pluginsDir, workingDirectory);
|
|
if (fromPlugin.Count > 0)
|
|
return fromPlugin;
|
|
|
|
var manifest = TryLoadManifest(launcher);
|
|
return manifest.Tables
|
|
.Where(t => !string.IsNullOrWhiteSpace(t.Id) && !string.IsNullOrWhiteSpace(t.FileName))
|
|
.Select(e => ToDef(e, pluginsDir, workingDirectory))
|
|
.ToList();
|
|
}
|
|
|
|
public static long ComputeManifestSignature(Simple3Launcher launcher)
|
|
{
|
|
long sig = 0;
|
|
foreach (var path in ResolveManifestPaths(launcher))
|
|
{
|
|
if (!File.Exists(path)) continue;
|
|
try
|
|
{
|
|
var info = new FileInfo(path);
|
|
sig ^= info.LastWriteTimeUtc.Ticks;
|
|
sig ^= info.Length;
|
|
}
|
|
catch { /* ignore */ }
|
|
}
|
|
|
|
var pluginsDir = launcher.ResolvePluginsDir();
|
|
var dll = pluginsDir == null ? null : Path.Combine(pluginsDir, "StandardScene.Signal.dll");
|
|
if (dll != null && File.Exists(dll))
|
|
{
|
|
try
|
|
{
|
|
var info = new FileInfo(dll);
|
|
sig ^= info.LastWriteTimeUtc.Ticks;
|
|
}
|
|
catch { /* ignore */ }
|
|
}
|
|
|
|
return sig;
|
|
}
|
|
|
|
private static SignalTableManifest TryLoadManifest(Simple3Launcher launcher)
|
|
{
|
|
foreach (var path in ResolveManifestPaths(launcher))
|
|
{
|
|
if (!File.Exists(path)) continue;
|
|
try
|
|
{
|
|
var json = File.ReadAllText(path);
|
|
var manifest = JsonSerializer.Deserialize<SignalTableManifest>(json, JsonOptions);
|
|
if (manifest?.Tables is { Count: > 0 })
|
|
return manifest;
|
|
}
|
|
catch { /* try next */ }
|
|
}
|
|
|
|
return JsonSerializer.Deserialize<SignalTableManifest>(EmbeddedFallbackJson, JsonOptions)
|
|
?? new SignalTableManifest();
|
|
}
|
|
|
|
private static IEnumerable<string> ResolveManifestPaths(Simple3Launcher launcher)
|
|
{
|
|
var wd = launcher.ResolveWorkingDirectory();
|
|
if (!string.IsNullOrWhiteSpace(wd))
|
|
yield return Path.Combine(wd, "Config", "Signal", ManifestFileName);
|
|
|
|
var plugins = launcher.ResolvePluginsDir();
|
|
if (string.IsNullOrWhiteSpace(plugins)) yield break;
|
|
|
|
yield return Path.Combine(plugins, ManifestFileName);
|
|
yield return Path.Combine(plugins, "Config", "Signal", ManifestFileName);
|
|
}
|
|
|
|
private static SignalTableDef ToDef(SignalTableManifestEntry entry, string? pluginsDir, string? workingDirectory)
|
|
{
|
|
var columns = BuildColumns(entry, pluginsDir, workingDirectory);
|
|
return new SignalTableDef(
|
|
entry.Id.Trim(),
|
|
string.IsNullOrWhiteSpace(entry.Title) ? entry.Id.Trim() : entry.Title.Trim(),
|
|
entry.Category ?? "",
|
|
entry.FileName.Trim(),
|
|
columns);
|
|
}
|
|
|
|
private static IReadOnlyList<SignalColumn> BuildColumns(
|
|
SignalTableManifestEntry entry,
|
|
string? pluginsDir,
|
|
string? workingDirectory)
|
|
{
|
|
var reflected = SignalModelSchemaResolver.ResolveColumns(entry.Model, pluginsDir, workingDirectory);
|
|
if (reflected.Count > 0)
|
|
return reflected;
|
|
|
|
if (entry.Columns is { Count: > 0 })
|
|
{
|
|
return entry.Columns
|
|
.Where(c => !string.IsNullOrWhiteSpace(c.Key))
|
|
.Select(c => new SignalColumn(
|
|
c.Key.Trim(),
|
|
string.IsNullOrWhiteSpace(c.Label) ? c.Key.Trim() : c.Label.Trim(),
|
|
string.IsNullOrWhiteSpace(c.Type) ? "string" : c.Type.Trim(),
|
|
c.Options,
|
|
string.IsNullOrWhiteSpace(c.Group) ? null : c.Group.Trim()))
|
|
.ToList();
|
|
}
|
|
|
|
return Array.Empty<SignalColumn>();
|
|
}
|
|
|
|
private const string EmbeddedFallbackJson = """
|
|
{
|
|
"version": 1,
|
|
"tables": [
|
|
{
|
|
"id": "stations",
|
|
"title": "PLC机构",
|
|
"category": "PLC数据管理",
|
|
"fileName": "stations.json",
|
|
"model": "PlcStationModel"
|
|
},
|
|
{
|
|
"id": "docks",
|
|
"title": "机构工位",
|
|
"category": "PLC数据管理",
|
|
"fileName": "station-docks.json",
|
|
"model": "PlcStationDockModel"
|
|
},
|
|
{
|
|
"id": "handshake",
|
|
"title": "握手点",
|
|
"category": "握手点数据管理",
|
|
"fileName": "handshake-points.json",
|
|
"model": "HandshakePointModel"
|
|
},
|
|
{
|
|
"id": "release",
|
|
"title": "放行点",
|
|
"category": "放行点数据管理",
|
|
"fileName": "release-points.json",
|
|
"model": "ReleasePointModel"
|
|
},
|
|
{
|
|
"id": "mag-control",
|
|
"title": "磁条管控区",
|
|
"category": "磁条交管",
|
|
"fileName": "mag-control-areas.json",
|
|
"model": "MagControlAreaModel"
|
|
}
|
|
]
|
|
}
|
|
""";
|
|
}
|