106 lines
4.4 KiB
C#
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) { }
|
|
}
|
|
}
|
|
}
|
|
}
|