feat: add ERP agent pet bridge and startup guide
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
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) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user