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 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 _serverData; public CommandPlan() { PlanId = Guid.NewGuid().ToString("N"); CreatedAtUtc = DateTime.UtcNow; ExpiresAtUtc = CreatedAtUtc.AddMinutes(10); Data = new Dictionary(StringComparer.OrdinalIgnoreCase); Warnings = new List(); _serverData = new Dictionary(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 Data { get; private set; } public IList 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(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(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(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 Data { get; private set; } } public interface ICommandHandler { CommandDescriptor Descriptor { get; } CommandPlan Plan(IDictionary input, CommandExecutionContext context); CommandResult Execute(CommandPlan plan, CommandExecutionContext context); } public sealed class CommandFollowupRequest { public CommandFollowupRequest() { Input = new Dictionary(StringComparer.OrdinalIgnoreCase); } public string CommandName { get; set; } public IDictionary Input { get; private set; } } /// /// 写命令成功后可生成一个新的、仍需独立预览和确认的后续计划。 /// 返回值只描述下一条命令,绝不能在此接口内执行副作用。 /// 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 _handlers = new Dictionary(StringComparer.OrdinalIgnoreCase); public void Register(ICommandHandler handler) { RegisterMany(new[] { handler }); } public void RegisterMany(IEnumerable handlers) { if (handlers == null) throw new ArgumentNullException("handlers"); List items = handlers.ToList(); if (items.Count == 0 || items.Any(handler => handler == null)) { throw new ArgumentException("命令处理器不能为空。", "handlers"); } List 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 List() { lock (_syncRoot) { List result = new List(); 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 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); } } } }