From 1add36c9559f0112f5bcf4d263da14337b14f68f Mon Sep 17 00:00:00 2001
From: root <295172551@qq.com>
Date: Fri, 13 Jun 2025 22:59:05 +0800
Subject: [PATCH] Update command executors and interfaces
---
.../System/Command/ISystemCommandExecutor.cs | 9 ++-
.../Command/Base/BaseCommandExecutor.cs | 50 ++++++++++--
.../Command/Executors/LinuxCommandExecutor.cs | 78 ++++++++++++++++---
.../Executors/WindowsCommandExecutor.cs | 68 ++++++++++++++--
4 files changed, 179 insertions(+), 26 deletions(-)
diff --git a/CoreAgent.Domain/Interfaces/System/Command/ISystemCommandExecutor.cs b/CoreAgent.Domain/Interfaces/System/Command/ISystemCommandExecutor.cs
index 13a3e5a..3ebe24e 100644
--- a/CoreAgent.Domain/Interfaces/System/Command/ISystemCommandExecutor.cs
+++ b/CoreAgent.Domain/Interfaces/System/Command/ISystemCommandExecutor.cs
@@ -11,16 +11,19 @@ public interface ISystemCommandExecutor
/// 执行命令并实时输出结果
///
/// 要执行的命令
- /// 输出处理器
+ /// 标准输出处理器
+ /// 标准错误处理器
/// 取消令牌源
+ /// 超时时间(毫秒),-1表示不设置超时
/// 执行任务
- Task ExecuteCommandWithOutputAsync(string command, Action outputHandler, CancellationTokenSource cancellationTokenSource);
+ Task ExecuteCommandWithOutputAsync(string command, Action outputHandler, Action errorHandler, CancellationTokenSource cancellationTokenSource, int timeout = -1);
///
/// 执行命令并返回结果
///
/// 要执行的命令
/// 取消令牌源
+ /// 超时时间(毫秒),-1表示不设置超时
/// 命令执行结果
- Task ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource);
+ Task ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource, int timeout = -1);
}
\ No newline at end of file
diff --git a/CoreAgent.Infrastructure/Command/Base/BaseCommandExecutor.cs b/CoreAgent.Infrastructure/Command/Base/BaseCommandExecutor.cs
index f96b9e0..8175f82 100644
--- a/CoreAgent.Infrastructure/Command/Base/BaseCommandExecutor.cs
+++ b/CoreAgent.Infrastructure/Command/Base/BaseCommandExecutor.cs
@@ -17,23 +17,55 @@ public abstract class BaseCommandExecutor : ISystemCommandExecutor
protected BaseCommandExecutor(ILogger logger)
{
- _logger = logger;
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
- public abstract Task ExecuteCommandWithOutputAsync(string command, Action outputHandler, CancellationTokenSource cancellationTokenSource);
+ ///
+ /// 执行命令并实时输出结果
+ ///
+ public abstract Task ExecuteCommandWithOutputAsync(string command, Action outputHandler, Action errorHandler, CancellationTokenSource cancellationTokenSource, int timeout = -1);
- public abstract Task ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource);
+ ///
+ /// 执行命令并返回结果
+ ///
+ public abstract Task ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource, int timeout = -1);
- protected async Task ExecuteCommandInternalAsync(string command, string arguments, CancellationTokenSource cancellationTokenSource)
+ ///
+ /// 获取命令参数,由子类实现具体的命令参数格式化逻辑
+ ///
+ protected abstract string GetCommandArguments(string command);
+
+ ///
+ /// 执行命令的通用逻辑
+ ///
+ protected virtual async Task ExecuteCommandInternalAsync(string command, string arguments, CancellationTokenSource cancellationTokenSource, int timeout = -1)
{
+ if (string.IsNullOrWhiteSpace(command))
+ {
+ throw new ArgumentException("命令不能为空", nameof(command));
+ }
+
+ if (cancellationTokenSource == null)
+ {
+ throw new ArgumentNullException(nameof(cancellationTokenSource));
+ }
+
var startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
try
{
_logger.LogDebug("开始执行命令: {Command}", command);
+
var cmd = Cli.Wrap(DefaultShell)
.WithArguments(arguments)
.WithValidation(CommandResultValidation.None);
- var result = await cmd.ExecuteBufferedAsync(cancellationTokenSource.Token);
+
+ // 如果设置了超时,创建一个新的 CancellationTokenSource
+ using var timeoutCts = timeout > 0 ? new CancellationTokenSource(timeout) : null;
+ using var linkedCts = timeout > 0
+ ? CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token, timeoutCts!.Token)
+ : cancellationTokenSource;
+
+ var result = await cmd.ExecuteBufferedAsync(linkedCts.Token);
var executionTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTime;
if (result.IsSuccess)
@@ -47,10 +79,16 @@ public abstract class BaseCommandExecutor : ISystemCommandExecutor
return CommandExecutionResult.Failure(result.StandardError, executionTime);
}
}
+ catch (OperationCanceledException ex)
+ {
+ var executionTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTime;
+ _logger.LogError(ex, "命令执行超时: {Command}", command);
+ return CommandExecutionResult.Failure("命令执行超时", executionTime);
+ }
catch (Exception ex)
{
var executionTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTime;
- _logger.LogError(ex, "命令执行失败: {Command}", command);
+ _logger.LogError(ex, "命令执行失败: {Command}, 错误: {ErrorMessage}", command, ex.Message);
return CommandExecutionResult.Failure(ex.Message, executionTime);
}
}
diff --git a/CoreAgent.Infrastructure/Command/Executors/LinuxCommandExecutor.cs b/CoreAgent.Infrastructure/Command/Executors/LinuxCommandExecutor.cs
index bac0078..70f6c77 100644
--- a/CoreAgent.Infrastructure/Command/Executors/LinuxCommandExecutor.cs
+++ b/CoreAgent.Infrastructure/Command/Executors/LinuxCommandExecutor.cs
@@ -4,6 +4,7 @@ using CoreAgent.Domain.Interfaces.System.Command;
using CoreAgent.Domain.Models.System;
using CoreAgent.Infrastructure.Command.Base;
using Microsoft.Extensions.Logging;
+using System.IO;
namespace CoreAgent.Infrastructure.Command.Executors;
@@ -12,35 +13,92 @@ namespace CoreAgent.Infrastructure.Command.Executors;
///
public class LinuxCommandExecutor : BaseCommandExecutor
{
- private const string ShellPath = "/bin/bash";
+ private const string BashPath = "/bin/bash";
+ private const string ShPath = "/bin/sh";
+ private readonly string _shellPath;
public LinuxCommandExecutor(ILogger logger) : base(logger)
{
+ // 优先使用 bash,如果不存在则使用 sh
+ _shellPath = File.Exists(BashPath) ? BashPath : ShPath;
+ _logger.LogInformation("使用Shell: {ShellPath}", _shellPath);
}
- protected override string DefaultShell => ShellPath;
+ protected override string DefaultShell => _shellPath;
- public override async Task ExecuteCommandWithOutputAsync(string command, Action outputHandler, CancellationTokenSource cancellationTokenSource)
+ public override async Task ExecuteCommandWithOutputAsync(string command, Action outputHandler, Action errorHandler, CancellationTokenSource cancellationTokenSource, int timeout = -1)
{
+ if (string.IsNullOrWhiteSpace(command))
+ {
+ throw new ArgumentException("命令不能为空", nameof(command));
+ }
+
+ if (outputHandler == null)
+ {
+ throw new ArgumentNullException(nameof(outputHandler));
+ }
+
+ if (errorHandler == null)
+ {
+ throw new ArgumentNullException(nameof(errorHandler));
+ }
+
+ if (cancellationTokenSource == null)
+ {
+ throw new ArgumentNullException(nameof(cancellationTokenSource));
+ }
+
try
{
_logger.LogDebug("开始执行命令: {Command}", command);
- var cmd = Cli.Wrap(DefaultShell)
- .WithArguments($"-c \"{command}\"")
+
+ var cmd = Cli.Wrap(_shellPath)
+ .WithArguments(GetCommandArguments(command))
.WithStandardOutputPipe(PipeTarget.ToDelegate(outputHandler))
- .WithStandardErrorPipe(PipeTarget.ToDelegate(outputHandler))
+ .WithStandardErrorPipe(PipeTarget.ToDelegate(errorHandler))
.WithValidation(CommandResultValidation.None);
- var result = await cmd.ExecuteBufferedAsync(cancellationTokenSource.Token);
+
+ // 如果设置了超时,创建一个新的 CancellationTokenSource
+ using var timeoutCts = timeout > 0 ? new CancellationTokenSource(timeout) : null;
+ using var linkedCts = timeout > 0
+ ? CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token, timeoutCts!.Token)
+ : cancellationTokenSource;
+
+ var result = await cmd.ExecuteBufferedAsync(linkedCts.Token);
_logger.LogInformation("命令执行结果: {IsSuccess}", result.IsSuccess);
}
+ catch (OperationCanceledException ex)
+ {
+ _logger.LogError(ex, "命令执行超时: {Command}", command);
+ throw;
+ }
catch (Exception ex)
{
- _logger.LogError(ex, "命令执行失败: {Command}", command);
+ _logger.LogError(ex, "命令执行失败: {Command}, 错误: {ErrorMessage}", command, ex.Message);
+ throw;
}
}
- public override async Task ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource)
+ public override async Task ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource, int timeout = -1)
{
- return await ExecuteCommandInternalAsync(command, $"-c \"{command}\"", cancellationTokenSource);
+ return await ExecuteCommandInternalAsync(command, GetCommandArguments(command), cancellationTokenSource, timeout);
+ }
+
+ protected override string GetCommandArguments(string command)
+ {
+ if (string.IsNullOrWhiteSpace(command))
+ {
+ throw new ArgumentException("命令不能为空", nameof(command));
+ }
+
+ // 转义命令中的特殊字符
+ var escapedCommand = command
+ .Replace("\"", "\\\"")
+ .Replace("'", "\\'")
+ .Replace("$", "\\$")
+ .Replace("`", "\\`")
+ .Replace("\\", "\\\\");
+
+ return $"-c '{escapedCommand}'";
}
}
\ No newline at end of file
diff --git a/CoreAgent.Infrastructure/Command/Executors/WindowsCommandExecutor.cs b/CoreAgent.Infrastructure/Command/Executors/WindowsCommandExecutor.cs
index 35c5687..3fd86e8 100644
--- a/CoreAgent.Infrastructure/Command/Executors/WindowsCommandExecutor.cs
+++ b/CoreAgent.Infrastructure/Command/Executors/WindowsCommandExecutor.cs
@@ -21,27 +21,81 @@ public class WindowsCommandExecutor : BaseCommandExecutor
protected override string DefaultShell => ShellPath;
- public override async Task ExecuteCommandWithOutputAsync(string command, Action outputHandler, CancellationTokenSource cancellationTokenSource)
+ public override async Task ExecuteCommandWithOutputAsync(string command, Action outputHandler, Action errorHandler, CancellationTokenSource cancellationTokenSource, int timeout = -1)
{
+ if (string.IsNullOrWhiteSpace(command))
+ {
+ throw new ArgumentException("命令不能为空", nameof(command));
+ }
+
+ if (outputHandler == null)
+ {
+ throw new ArgumentNullException(nameof(outputHandler));
+ }
+
+ if (errorHandler == null)
+ {
+ throw new ArgumentNullException(nameof(errorHandler));
+ }
+
+ if (cancellationTokenSource == null)
+ {
+ throw new ArgumentNullException(nameof(cancellationTokenSource));
+ }
+
try
{
_logger.LogDebug("开始执行命令: {Command}", command);
+
var cmd = Cli.Wrap(DefaultShell)
- .WithArguments($"/c {command}")
+ .WithArguments(GetCommandArguments(command))
.WithStandardOutputPipe(PipeTarget.ToDelegate(outputHandler))
- .WithStandardErrorPipe(PipeTarget.ToDelegate(outputHandler))
+ .WithStandardErrorPipe(PipeTarget.ToDelegate(errorHandler))
.WithValidation(CommandResultValidation.None);
- var result = await cmd.ExecuteBufferedAsync(cancellationTokenSource.Token);
+
+ // 如果设置了超时,创建一个新的 CancellationTokenSource
+ using var timeoutCts = timeout > 0 ? new CancellationTokenSource(timeout) : null;
+ using var linkedCts = timeout > 0
+ ? CancellationTokenSource.CreateLinkedTokenSource(cancellationTokenSource.Token, timeoutCts!.Token)
+ : cancellationTokenSource;
+
+ var result = await cmd.ExecuteBufferedAsync(linkedCts.Token);
_logger.LogInformation("命令执行结果: {IsSuccess}", result.IsSuccess);
}
+ catch (OperationCanceledException ex)
+ {
+ _logger.LogError(ex, "命令执行超时: {Command}", command);
+ throw;
+ }
catch (Exception ex)
{
- _logger.LogError(ex, "命令执行失败: {Command}", command);
+ _logger.LogError(ex, "命令执行失败: {Command}, 错误: {ErrorMessage}", command, ex.Message);
+ throw;
}
}
- public override async Task ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource)
+ public override async Task ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource, int timeout = -1)
{
- return await ExecuteCommandInternalAsync(command, $"/c {command}", cancellationTokenSource);
+ return await ExecuteCommandInternalAsync(command, GetCommandArguments(command), cancellationTokenSource, timeout);
+ }
+
+ protected override string GetCommandArguments(string command)
+ {
+ if (string.IsNullOrWhiteSpace(command))
+ {
+ throw new ArgumentException("命令不能为空", nameof(command));
+ }
+
+ // Windows CMD 命令转义
+ var escapedCommand = command
+ .Replace("\"", "\\\"")
+ .Replace("^", "^^")
+ .Replace("&", "^&")
+ .Replace("|", "^|")
+ .Replace("<", "^<")
+ .Replace(">", "^>")
+ .Replace("%", "%%");
+
+ return $"/c \"{escapedCommand}\"";
}
}
\ No newline at end of file