Browse Source

Update command executors and interfaces

master
root 4 days ago
parent
commit
1add36c955
  1. 9
      CoreAgent.Domain/Interfaces/System/Command/ISystemCommandExecutor.cs
  2. 50
      CoreAgent.Infrastructure/Command/Base/BaseCommandExecutor.cs
  3. 78
      CoreAgent.Infrastructure/Command/Executors/LinuxCommandExecutor.cs
  4. 68
      CoreAgent.Infrastructure/Command/Executors/WindowsCommandExecutor.cs

9
CoreAgent.Domain/Interfaces/System/Command/ISystemCommandExecutor.cs

@ -11,16 +11,19 @@ public interface ISystemCommandExecutor
/// 执行命令并实时输出结果 /// 执行命令并实时输出结果
/// </summary> /// </summary>
/// <param name="command">要执行的命令</param> /// <param name="command">要执行的命令</param>
/// <param name="outputHandler">输出处理器</param> /// <param name="outputHandler">标准输出处理器</param>
/// <param name="errorHandler">标准错误处理器</param>
/// <param name="cancellationTokenSource">取消令牌源</param> /// <param name="cancellationTokenSource">取消令牌源</param>
/// <param name="timeout">超时时间(毫秒),-1表示不设置超时</param>
/// <returns>执行任务</returns> /// <returns>执行任务</returns>
Task ExecuteCommandWithOutputAsync(string command, Action<string> outputHandler, CancellationTokenSource cancellationTokenSource); Task ExecuteCommandWithOutputAsync(string command, Action<string> outputHandler, Action<string> errorHandler, CancellationTokenSource cancellationTokenSource, int timeout = -1);
/// <summary> /// <summary>
/// 执行命令并返回结果 /// 执行命令并返回结果
/// </summary> /// </summary>
/// <param name="command">要执行的命令</param> /// <param name="command">要执行的命令</param>
/// <param name="cancellationTokenSource">取消令牌源</param> /// <param name="cancellationTokenSource">取消令牌源</param>
/// <param name="timeout">超时时间(毫秒),-1表示不设置超时</param>
/// <returns>命令执行结果</returns> /// <returns>命令执行结果</returns>
Task<CommandExecutionResult> ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource); Task<CommandExecutionResult> ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource, int timeout = -1);
} }

50
CoreAgent.Infrastructure/Command/Base/BaseCommandExecutor.cs

@ -17,23 +17,55 @@ public abstract class BaseCommandExecutor : ISystemCommandExecutor
protected BaseCommandExecutor(ILogger logger) protected BaseCommandExecutor(ILogger logger)
{ {
_logger = logger; _logger = logger ?? throw new ArgumentNullException(nameof(logger));
} }
public abstract Task ExecuteCommandWithOutputAsync(string command, Action<string> outputHandler, CancellationTokenSource cancellationTokenSource); /// <summary>
/// 执行命令并实时输出结果
/// </summary>
public abstract Task ExecuteCommandWithOutputAsync(string command, Action<string> outputHandler, Action<string> errorHandler, CancellationTokenSource cancellationTokenSource, int timeout = -1);
public abstract Task<CommandExecutionResult> ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource); /// <summary>
/// 执行命令并返回结果
/// </summary>
public abstract Task<CommandExecutionResult> ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource, int timeout = -1);
protected async Task<CommandExecutionResult> ExecuteCommandInternalAsync(string command, string arguments, CancellationTokenSource cancellationTokenSource) /// <summary>
/// 获取命令参数,由子类实现具体的命令参数格式化逻辑
/// </summary>
protected abstract string GetCommandArguments(string command);
/// <summary>
/// 执行命令的通用逻辑
/// </summary>
protected virtual async Task<CommandExecutionResult> 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(); var startTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
try try
{ {
_logger.LogDebug("开始执行命令: {Command}", command); _logger.LogDebug("开始执行命令: {Command}", command);
var cmd = Cli.Wrap(DefaultShell) var cmd = Cli.Wrap(DefaultShell)
.WithArguments(arguments) .WithArguments(arguments)
.WithValidation(CommandResultValidation.None); .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; var executionTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTime;
if (result.IsSuccess) if (result.IsSuccess)
@ -47,10 +79,16 @@ public abstract class BaseCommandExecutor : ISystemCommandExecutor
return CommandExecutionResult.Failure(result.StandardError, executionTime); 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) catch (Exception ex)
{ {
var executionTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - startTime; 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); return CommandExecutionResult.Failure(ex.Message, executionTime);
} }
} }

78
CoreAgent.Infrastructure/Command/Executors/LinuxCommandExecutor.cs

@ -4,6 +4,7 @@ using CoreAgent.Domain.Interfaces.System.Command;
using CoreAgent.Domain.Models.System; using CoreAgent.Domain.Models.System;
using CoreAgent.Infrastructure.Command.Base; using CoreAgent.Infrastructure.Command.Base;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.IO;
namespace CoreAgent.Infrastructure.Command.Executors; namespace CoreAgent.Infrastructure.Command.Executors;
@ -12,35 +13,92 @@ namespace CoreAgent.Infrastructure.Command.Executors;
/// </summary> /// </summary>
public class LinuxCommandExecutor : BaseCommandExecutor 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<LinuxCommandExecutor> logger) : base(logger) public LinuxCommandExecutor(ILogger<LinuxCommandExecutor> 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<string> outputHandler, CancellationTokenSource cancellationTokenSource) public override async Task ExecuteCommandWithOutputAsync(string command, Action<string> outputHandler, Action<string> 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 try
{ {
_logger.LogDebug("开始执行命令: {Command}", command); _logger.LogDebug("开始执行命令: {Command}", command);
var cmd = Cli.Wrap(DefaultShell)
.WithArguments($"-c \"{command}\"") var cmd = Cli.Wrap(_shellPath)
.WithArguments(GetCommandArguments(command))
.WithStandardOutputPipe(PipeTarget.ToDelegate(outputHandler)) .WithStandardOutputPipe(PipeTarget.ToDelegate(outputHandler))
.WithStandardErrorPipe(PipeTarget.ToDelegate(outputHandler)) .WithStandardErrorPipe(PipeTarget.ToDelegate(errorHandler))
.WithValidation(CommandResultValidation.None); .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); _logger.LogInformation("命令执行结果: {IsSuccess}", result.IsSuccess);
} }
catch (OperationCanceledException ex)
{
_logger.LogError(ex, "命令执行超时: {Command}", command);
throw;
}
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "命令执行失败: {Command}", command); _logger.LogError(ex, "命令执行失败: {Command}, 错误: {ErrorMessage}", command, ex.Message);
throw;
} }
} }
public override async Task<CommandExecutionResult> ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource) public override async Task<CommandExecutionResult> 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}'";
} }
} }

68
CoreAgent.Infrastructure/Command/Executors/WindowsCommandExecutor.cs

@ -21,27 +21,81 @@ public class WindowsCommandExecutor : BaseCommandExecutor
protected override string DefaultShell => ShellPath; protected override string DefaultShell => ShellPath;
public override async Task ExecuteCommandWithOutputAsync(string command, Action<string> outputHandler, CancellationTokenSource cancellationTokenSource) public override async Task ExecuteCommandWithOutputAsync(string command, Action<string> outputHandler, Action<string> 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 try
{ {
_logger.LogDebug("开始执行命令: {Command}", command); _logger.LogDebug("开始执行命令: {Command}", command);
var cmd = Cli.Wrap(DefaultShell) var cmd = Cli.Wrap(DefaultShell)
.WithArguments($"/c {command}") .WithArguments(GetCommandArguments(command))
.WithStandardOutputPipe(PipeTarget.ToDelegate(outputHandler)) .WithStandardOutputPipe(PipeTarget.ToDelegate(outputHandler))
.WithStandardErrorPipe(PipeTarget.ToDelegate(outputHandler)) .WithStandardErrorPipe(PipeTarget.ToDelegate(errorHandler))
.WithValidation(CommandResultValidation.None); .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); _logger.LogInformation("命令执行结果: {IsSuccess}", result.IsSuccess);
} }
catch (OperationCanceledException ex)
{
_logger.LogError(ex, "命令执行超时: {Command}", command);
throw;
}
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "命令执行失败: {Command}", command); _logger.LogError(ex, "命令执行失败: {Command}, 错误: {ErrorMessage}", command, ex.Message);
throw;
} }
} }
public override async Task<CommandExecutionResult> ExecuteCommandAsync(string command, CancellationTokenSource cancellationTokenSource) public override async Task<CommandExecutionResult> 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}\"";
} }
} }
Loading…
Cancel
Save