Files
lserp_cs_6.0/插件库/Lskj.CommandKernel/CommandContracts.cs
T
2026-08-14 14:28:28 +08:00

377 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
namespace Lskj.CommandKernel
{
public enum CommandRisk
{
Read = 0,
Navigate = 1,
Draft = 2,
Write = 3,
Critical = 4
}
public sealed class CommandDescriptor
{
public string Name { get; set; }
public string Version { get; set; }
public string Description { get; set; }
public string SchemaVersion { get; set; }
public IDictionary<string, object> InputSchema { get; set; }
public string RequiredPermission { get; set; }
public CommandRisk Risk { get; set; }
public bool RequiresConfirmation { get; set; }
public bool RequiresIdempotencyKey { get; set; }
}
public sealed class CommandExecutionContext
{
public CommandExecutionContext()
{
CorrelationId = Guid.NewGuid().ToString("N");
}
public string CorrelationId { get; set; }
public string UserId { get; set; }
public string UserName { get; set; }
public string AccountBook { get; set; }
public string SubSystemId { get; set; }
// 由受信任的 ERP 登录运行时根据实际数据库提供者、服务器和库名
// 计算;模型和命令输入均不得提供或覆盖该值。
public string DatabaseScopeFingerprint { get; set; }
public string IdempotencyKey { get; set; }
public string ConfirmationToken { get; set; }
public string ClientSessionId { get; set; }
// These fields are populated only by the in-process bridge after a
// customer-UAT grant has been verified. The lease is deliberately
// never projected or audited; guarded workflow gateways revalidate it
// immediately before every read/write procedure call.
public string UatAuthorizationIdSha256 { get; set; }
public string UatCaseCode { get; set; }
public string UatCommandName { get; set; }
public string UatExecutionLease { get; set; }
}
public sealed class CommandPlan
{
private readonly Dictionary<string, object> _serverData;
public CommandPlan()
{
PlanId = Guid.NewGuid().ToString("N");
CreatedAtUtc = DateTime.UtcNow;
ExpiresAtUtc = CreatedAtUtc.AddMinutes(10);
Data = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
Warnings = new List<string>();
_serverData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
public string PlanId { get; set; }
public string CommandName { get; set; }
public string CommandVersion { get; set; }
public string ModuleCode { get; set; }
public string OwnerClientSessionId { get; set; }
public string OwnerUserId { get; set; }
public string OwnerUserName { get; set; }
public string OwnerAccountBook { get; set; }
public string OwnerSubSystemId { get; set; }
public string OwnerDatabaseScopeFingerprint { get; set; }
public string CorrelationId { get; set; }
public string InputFingerprint { get; set; }
public CommandRisk Risk { get; set; }
public DateTime CreatedAtUtc { get; set; }
public DateTime ExpiresAtUtc { get; set; }
public bool Valid { get; set; }
public IDictionary<string, object> Data { get; private set; }
public IList<string> Warnings { get; private set; }
public void SetServerData(string key, object value)
{
if (string.IsNullOrWhiteSpace(key))
throw new ArgumentException("服务端计划数据键不能为空。", "key");
_serverData[key] = value;
}
public T GetServerData<T>(string key)
{
object value;
if (string.IsNullOrWhiteSpace(key) || !_serverData.TryGetValue(key, out value))
throw new CommandKernelException("plan_state_missing", "执行计划缺少服务端状态,请重新生成预览。", 6);
if (!(value is T))
throw new CommandKernelException("plan_state_invalid", "执行计划服务端状态类型无效,请重新生成预览。", 6);
return (T)value;
}
public bool TryGetServerData<T>(string key, out T value)
{
object stored;
if (!string.IsNullOrWhiteSpace(key)
&& _serverData.TryGetValue(key, out stored)
&& stored is T)
{
value = (T)stored;
return true;
}
value = default(T);
return false;
}
}
public sealed class CommandResult
{
public CommandResult()
{
Data = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
public bool Success { get; set; }
public string Code { get; set; }
public string Message { get; set; }
public string RecordId { get; set; }
public bool Replayed { get; set; }
public string TransactionEvidenceId { get; set; }
public string BusinessAuditId { get; set; }
public IDictionary<string, object> Data { get; private set; }
}
public interface ICommandHandler
{
CommandDescriptor Descriptor { get; }
CommandPlan Plan(IDictionary<string, object> input, CommandExecutionContext context);
CommandResult Execute(CommandPlan plan, CommandExecutionContext context);
}
public sealed class CommandFollowupRequest
{
public CommandFollowupRequest()
{
Input = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
public string CommandName { get; set; }
public IDictionary<string, object> Input { get; private set; }
}
/// <summary>
/// 写命令成功后可生成一个新的、仍需独立预览和确认的后续计划。
/// 返回值只描述下一条命令,绝不能在此接口内执行副作用。
/// </summary>
public interface ICommandExecutionFollowupProvider
{
bool TryCreateFollowup(
CommandPlan completedPlan,
CommandResult completedResult,
CommandExecutionContext context,
out CommandFollowupRequest followup);
}
public interface ICommandAuditSink
{
void Planned(CommandDescriptor descriptor, CommandPlan plan, CommandExecutionContext context);
void Completed(CommandDescriptor descriptor, CommandPlan plan, CommandResult result, CommandExecutionContext context);
void Failed(CommandDescriptor descriptor, CommandPlan plan, Exception exception, CommandExecutionContext context);
}
public enum IdempotencyClaimState
{
Acquired = 0,
Replay = 1,
InProgress = 2,
Conflict = 3
}
public sealed class IdempotencyClaim
{
public IdempotencyClaimState State { get; set; }
public CommandResult Result { get; set; }
}
public interface IIdempotencyStore
{
IdempotencyClaim Claim(
string commandName,
string idempotencyKey,
CommandExecutionContext context,
string inputFingerprint);
void Complete(
string commandName,
string idempotencyKey,
CommandExecutionContext context,
string inputFingerprint,
CommandResult result);
void Abandon(
string commandName,
string idempotencyKey,
CommandExecutionContext context,
string inputFingerprint);
}
public interface IConfirmationValidator
{
bool Validate(CommandPlan plan, CommandExecutionContext context, out string failureReason);
}
public interface IConfirmationTokenIssuer
{
string Issue(CommandPlan plan, CommandExecutionContext context, TimeSpan lifetime);
}
public sealed class CommandAuthorizationDecision
{
public static CommandAuthorizationDecision Allow()
{
return new CommandAuthorizationDecision { Allowed = true };
}
public static CommandAuthorizationDecision Deny(string code, string message)
{
return new CommandAuthorizationDecision
{
Allowed = false,
Code = code,
Message = message
};
}
public bool Allowed { get; set; }
public string Code { get; set; }
public string Message { get; set; }
}
public interface ICommandAuthorizer
{
CommandAuthorizationDecision Authorize(
CommandDescriptor descriptor,
CommandExecutionContext context,
bool execution);
}
public interface ISystemClock
{
DateTime UtcNow { get; }
}
public sealed class CommandRegistry
{
private readonly object _syncRoot = new object();
private readonly Dictionary<string, ICommandHandler> _handlers =
new Dictionary<string, ICommandHandler>(StringComparer.OrdinalIgnoreCase);
public void Register(ICommandHandler handler)
{
RegisterMany(new[] { handler });
}
public void RegisterMany(IEnumerable<ICommandHandler> handlers)
{
if (handlers == null) throw new ArgumentNullException("handlers");
List<ICommandHandler> items = handlers.ToList();
if (items.Count == 0 || items.Any(handler => handler == null))
{
throw new ArgumentException("命令处理器不能为空。", "handlers");
}
List<ICommandHandler> registrations = items.Select(handler =>
(ICommandHandler)new RegisteredCommandHandler(
handler,
CommandDescriptorContract.ValidateAndClone(handler.Descriptor)))
.ToList();
if (registrations.GroupBy(
handler => handler.Descriptor.Name,
StringComparer.OrdinalIgnoreCase)
.Any(group => group.Count() > 1))
{
throw new InvalidOperationException("批量注册包含重复命令。");
}
lock (_syncRoot)
{
foreach (ICommandHandler handler in registrations)
{
if (_handlers.ContainsKey(handler.Descriptor.Name))
throw new InvalidOperationException("命令已注册:" + handler.Descriptor.Name);
}
foreach (ICommandHandler handler in registrations)
_handlers.Add(handler.Descriptor.Name, handler);
}
}
public ICommandHandler Resolve(string commandName)
{
if (string.IsNullOrWhiteSpace(commandName)) return null;
lock (_syncRoot)
{
ICommandHandler handler;
return _handlers.TryGetValue(commandName, out handler) ? handler : null;
}
}
public IList<CommandDescriptor> List()
{
lock (_syncRoot)
{
List<CommandDescriptor> result = new List<CommandDescriptor>();
foreach (ICommandHandler handler in _handlers.Values)
result.Add(handler.Descriptor);
return result;
}
}
private sealed class RegisteredCommandHandler :
ICommandHandler,
ICommandExecutionFollowupProvider
{
private readonly ICommandHandler _inner;
private readonly CommandDescriptor _descriptor;
public RegisteredCommandHandler(
ICommandHandler inner,
CommandDescriptor descriptor)
{
_inner = inner;
_descriptor = descriptor;
}
public CommandDescriptor Descriptor
{
get { return CommandDescriptorContract.Clone(_descriptor); }
}
public CommandPlan Plan(
IDictionary<string, object> input,
CommandExecutionContext context)
{
return _inner.Plan(input, context);
}
public CommandResult Execute(
CommandPlan plan,
CommandExecutionContext context)
{
return _inner.Execute(plan, context);
}
public bool TryCreateFollowup(
CommandPlan completedPlan,
CommandResult completedResult,
CommandExecutionContext context,
out CommandFollowupRequest followup)
{
ICommandExecutionFollowupProvider provider =
_inner as ICommandExecutionFollowupProvider;
if (provider == null)
{
followup = null;
return false;
}
return provider.TryCreateFollowup(
completedPlan,
completedResult,
context,
out followup);
}
}
}
}