Files
lserp_cs_6.0/插件库/Lskj.AgentPet.Host.Core/ErpBridge/SessionBoundErpBridgeClient.cs
T
2026-08-14 14:28:28 +08:00

157 lines
5.8 KiB
C#

using System.Text.Json;
using System.Collections.Generic;
using System.Linq;
using Lskj.AgentPet.Host.Core.Configuration;
namespace Lskj.AgentPet.Host.Core.ErpBridge;
public sealed class SessionBoundErpBridgeClient : IErpBridgeClient
{
private readonly IErpBridgeClient _inner;
private readonly ErpSessionScopeBinding _scope;
public SessionBoundErpBridgeClient(
IErpBridgeClient inner,
ErpSessionScopeBinding scope)
{
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
_scope = scope ?? throw new ArgumentNullException(nameof(scope));
}
public async Task<JsonDocument> SendAsync(
JsonElement request,
CancellationToken cancellationToken = default)
{
string method = RequiredString(request, "method", 128);
if (string.Equals(method, "context.get", StringComparison.Ordinal))
{
using JsonDocument boundRequest = BindRequest(request);
JsonDocument context = await _inner.SendAsync(
boundRequest.RootElement,
cancellationToken)
.ConfigureAwait(false);
try
{
_scope.VerifyContextResponse(context.RootElement);
return context;
}
catch
{
context.Dispose();
throw;
}
}
string protocol = RequiredString(request, "protocolVersion", 16);
string clientSessionId = RequiredString(request, "clientSessionId", 128);
ErpSessionScopeSnapshot before = await ReadContextAsync(
protocol,
clientSessionId,
cancellationToken).ConfigureAwait(false);
JsonDocument? result = null;
try
{
using JsonDocument boundRequest = BindRequest(request);
result = await _inner.SendAsync(
boundRequest.RootElement,
cancellationToken)
.ConfigureAwait(false);
ErpSessionScopeSnapshot after;
try
{
after = await ReadContextAsync(
protocol,
clientSessionId,
cancellationToken).ConfigureAwait(false);
}
catch (HostError error) when (error.Code == "erp_session_scope_mismatch")
{
throw new HostError(
"erp_session_scope_changed",
"ERP 会话在桌宠操作期间发生切换,操作结果不会交给页面继续使用。",
error);
}
bool targetMayChangeUi = string.Equals(
method,
"command.execute",
StringComparison.Ordinal);
if (!before.SameSessionIdentity(after)
|| (!targetMayChangeUi && !before.SameUiState(after)))
{
throw new HostError(
"erp_session_scope_changed",
"ERP 会话在桌宠操作期间发生切换,操作结果不会交给页面继续使用。");
}
JsonDocument accepted = result;
result = null;
return accepted;
}
finally
{
result?.Dispose();
}
}
private async Task<ErpSessionScopeSnapshot> ReadContextAsync(
string protocol,
string clientSessionId,
CancellationToken cancellationToken)
{
string requestId = Guid.NewGuid().ToString("N");
string correlationId = "scope-" + Guid.NewGuid().ToString("N");
using JsonDocument request = JsonSerializer.SerializeToDocument(new
{
protocolVersion = protocol,
requestId,
correlationId,
clientSessionId,
sessionScopeToken = _scope.Token,
method = "context.get",
payload = new { }
});
using JsonDocument response = await _inner.SendAsync(
request.RootElement,
cancellationToken).ConfigureAwait(false);
return _scope.VerifyContextResponse(response.RootElement);
}
private JsonDocument BindRequest(JsonElement request)
{
if (request.ValueKind != JsonValueKind.Object)
throw new HostError(
"bridge_request_invalid",
"ERP 桥请求必须是 JSON 对象。");
Dictionary<string, JsonElement> fields = new(StringComparer.Ordinal);
foreach (JsonProperty property in request.EnumerateObject())
{
if (!fields.TryAdd(property.Name, property.Value.Clone()))
throw new HostError(
"bridge_request_invalid",
"ERP 桥请求包含重复字段。");
}
// 浏览器页面不能声明或覆盖会话令牌;Host 只使用启动时绑定的
// ERP 登录范围重新注入它,然后才把请求交给命名管道客户端。
fields["sessionScopeToken"] = JsonSerializer.SerializeToElement(
_scope.Token);
return JsonSerializer.SerializeToDocument(fields);
}
private static string RequiredString(JsonElement source, string name, int maximumLength)
{
if (source.ValueKind != JsonValueKind.Object
|| !source.TryGetProperty(name, out JsonElement value)
|| value.ValueKind != JsonValueKind.String)
{
throw new HostError("bridge_request_invalid", "ERP 桥请求缺少会话绑定字段。");
}
string result = value.GetString() ?? string.Empty;
if (string.IsNullOrWhiteSpace(result)
|| result.Length > maximumLength
|| result.Any(char.IsControl))
throw new HostError("bridge_request_invalid", "ERP 桥请求会话绑定字段无效。");
return result;
}
}