Files
lserp_cs_6.0/插件库/Lskj.AgentBridge/BridgeDiscoveryPublisher.cs
2026-08-14 14:28:28 +08:00

106 lines
4.4 KiB
C#

using System;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
namespace Lskj.AgentBridge
{
/// <summary>
/// Publishes the local ERP bridge discovery document as one durable,
/// atomic filesystem transition. Readers therefore observe either the
/// previous complete generation or the new complete generation.
/// </summary>
public static class BridgeDiscoveryPublisher
{
internal const int MaximumDocumentBytes = 4096;
private static readonly Regex SafePipeName = new Regex(
"^[A-Za-z0-9_.-]{1,128}$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
private static readonly Regex SafeBridgeInstanceId = new Regex(
"^[a-f0-9]{32}$",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
public static string Publish(
string directory,
string pipeName,
int processId,
DateTime processStartedAtUtc,
string bridgeInstanceId)
{
if (string.IsNullOrWhiteSpace(directory)
|| !Path.IsPathRooted(directory))
throw new ArgumentException("发现目录必须是绝对路径。", "directory");
if (string.IsNullOrWhiteSpace(pipeName)
|| !SafePipeName.IsMatch(pipeName))
throw new ArgumentException("命名管道名称无效。", "pipeName");
if (processId <= 0)
throw new ArgumentOutOfRangeException("processId");
if (string.IsNullOrWhiteSpace(bridgeInstanceId)
|| !SafeBridgeInstanceId.IsMatch(bridgeInstanceId))
throw new ArgumentException("ERP 桥实例标识无效。", "bridgeInstanceId");
string expectedPipeName = "lserp.agent."
+ processId.ToString(CultureInfo.InvariantCulture)
+ "."
+ bridgeInstanceId;
if (!string.Equals(pipeName, expectedPipeName, StringComparison.Ordinal))
throw new ArgumentException(
"命名管道名称必须绑定 ERP 进程号和桥实例。",
"pipeName");
if (processStartedAtUtc.Kind != DateTimeKind.Utc)
throw new ArgumentException("ERP 进程启动时间必须是 UTC。", "processStartedAtUtc");
string fullDirectory = Path.GetFullPath(directory);
Directory.CreateDirectory(fullDirectory);
string destination = Path.Combine(
fullDirectory,
"agentbridge-" + processId.ToString(CultureInfo.InvariantCulture) + ".json");
string temporary = Path.Combine(
fullDirectory,
".agentbridge-" + processId.ToString(CultureInfo.InvariantCulture)
+ "-" + Guid.NewGuid().ToString("N") + ".tmp");
string json = JsonConvert.SerializeObject(new
{
protocolVersion = AgentBridgeRuntime.ProtocolVersion,
pipeName = pipeName,
processId = processId,
startedAtUtc = processStartedAtUtc.ToString("o", CultureInfo.InvariantCulture),
bridgeInstanceId = bridgeInstanceId
});
byte[] body = BridgeUtf8Codec.Encode(json);
if (body.Length == 0 || body.Length > MaximumDocumentBytes)
throw new InvalidDataException("ERP 桥发现文档长度无效。");
try
{
using (FileStream stream = new FileStream(
temporary,
FileMode.CreateNew,
FileAccess.Write,
FileShare.None,
4096,
FileOptions.WriteThrough))
{
stream.Write(body, 0, body.Length);
stream.Flush(true);
}
if (File.Exists(destination))
File.Replace(temporary, destination, null, true);
else
File.Move(temporary, destination);
return destination;
}
finally
{
try
{
if (File.Exists(temporary)) File.Delete(temporary);
}
catch (IOException) { }
catch (UnauthorizedAccessException) { }
}
}
}
}