From 55f15f62067c6ae5fc2a7c0ecbbc0a2906a7ff85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=BE=89=E9=B8=AD=E8=9B=8B?= Date: Wed, 27 Aug 2025 02:36:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E7=AE=A1=E7=90=86=E4=B8=8E?= =?UTF-8?q?=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Model/Gear/Tasks/GearTrigger.cs | 9 - .../Service/GearTask/GearTaskConverter.cs | 329 ++++++++++++ .../GearTask/GearTaskExecutionManager.cs | 487 ++++++++++++++++++ .../Service/GearTask/GearTaskExecutor.cs | 289 +++++++++++ .../GearTask/GearTaskExecutor_Usage.md | 329 ++++++++++++ .../Service/GearTask/GearTaskFactory.cs | 251 +++++++++ .../GearTask/GearTaskServiceExtensions.cs | 78 +++ .../{ => GearTask}/GearTaskStorageService.cs | 0 8 files changed, 1763 insertions(+), 9 deletions(-) delete mode 100644 BetterGenshinImpact/Model/Gear/Tasks/GearTrigger.cs create mode 100644 BetterGenshinImpact/Service/GearTask/GearTaskConverter.cs create mode 100644 BetterGenshinImpact/Service/GearTask/GearTaskExecutionManager.cs create mode 100644 BetterGenshinImpact/Service/GearTask/GearTaskExecutor.cs create mode 100644 BetterGenshinImpact/Service/GearTask/GearTaskExecutor_Usage.md create mode 100644 BetterGenshinImpact/Service/GearTask/GearTaskFactory.cs create mode 100644 BetterGenshinImpact/Service/GearTask/GearTaskServiceExtensions.cs rename BetterGenshinImpact/Service/{ => GearTask}/GearTaskStorageService.cs (100%) diff --git a/BetterGenshinImpact/Model/Gear/Tasks/GearTrigger.cs b/BetterGenshinImpact/Model/Gear/Tasks/GearTrigger.cs deleted file mode 100644 index 13fe5322..00000000 --- a/BetterGenshinImpact/Model/Gear/Tasks/GearTrigger.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace BetterGenshinImpact.Model.Gear.Tasks; - -/// -/// 为了和其他Trigger做区分,使用Gear(齿轮)来作为前缀命名调度器内定义的触发器 -/// -public class GearTrigger -{ - -} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/GearTask/GearTaskConverter.cs b/BetterGenshinImpact/Service/GearTask/GearTaskConverter.cs new file mode 100644 index 00000000..14464144 --- /dev/null +++ b/BetterGenshinImpact/Service/GearTask/GearTaskConverter.cs @@ -0,0 +1,329 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using BetterGenshinImpact.Model.Gear; +using BetterGenshinImpact.Model.Gear.Tasks; +using BetterGenshinImpact.Model.Gear.Parameter; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace BetterGenshinImpact.Service; + +/// +/// 齿轮任务转换器,负责将 GearTaskData 转换为可执行的 BaseGearTask +/// +public class GearTaskConverter +{ + private readonly ILogger _logger; + private readonly GearTaskFactory _taskFactory; + + public GearTaskConverter(ILogger logger, GearTaskFactory taskFactory) + { + _logger = logger; + _taskFactory = taskFactory; + } + + /// + /// 将 GearTaskDefinitionData 转换为可执行的任务列表 + /// + /// 任务定义数据 + /// 可执行的任务列表 + public async Task> ConvertTaskDefinitionAsync(GearTaskDefinitionData taskDefinition) + { + if (taskDefinition?.RootTask == null) + { + throw new ArgumentException("任务定义或根任务不能为空"); + } + + var tasks = new List(); + + try + { + _logger.LogInformation("开始转换任务定义: {TaskDefinitionName}", taskDefinition.Name); + + var rootTask = await ConvertTaskDataAsync(taskDefinition.RootTask); + tasks.Add(rootTask); + + _logger.LogInformation("任务定义转换完成: {TaskDefinitionName}, 共 {TaskCount} 个任务", + taskDefinition.Name, CountTotalTasks(rootTask)); + + return tasks; + } + catch (Exception ex) + { + _logger.LogError(ex, "转换任务定义失败: {TaskDefinitionName}", taskDefinition.Name); + throw; + } + } + + /// + /// 将单个 GearTaskData 转换为 BaseGearTask(包括子任务) + /// + /// 任务数据 + /// 父任务 + /// 转换后的任务 + public async Task ConvertTaskDataAsync(GearTaskData taskData, BaseGearTask? parent = null) + { + if (taskData == null) + { + throw new ArgumentNullException(nameof(taskData)); + } + + try + { + BaseGearTask task; + + // 如果是目录类型或者任务被禁用,创建容器任务 + if (taskData.IsDirectory || !taskData.IsEnabled) + { + task = new ContainerGearTask + { + Name = taskData.Name, + Type = taskData.TaskType ?? "container", + Enabled = taskData.IsEnabled, + Father = parent + }; + + _logger.LogDebug("创建容器任务: {TaskName} (IsDirectory: {IsDirectory}, IsEnabled: {IsEnabled})", + taskData.Name, taskData.IsDirectory, taskData.IsEnabled); + } + else + { + // 使用工厂创建具体的任务实例 + task = await _taskFactory.CreateTaskAsync(taskData); + task.Father = parent; + + _logger.LogDebug("创建具体任务: {TaskName} ({TaskType})", taskData.Name, taskData.TaskType); + } + + // 递归处理子任务 + if (taskData.Children?.Count > 0) + { + _logger.LogDebug("处理子任务: {TaskName}, 子任务数量: {ChildCount}", + taskData.Name, taskData.Children.Count); + + foreach (var childData in taskData.Children) + { + try + { + var childTask = await ConvertTaskDataAsync(childData, task); + task.Children.Add(childTask); + } + catch (Exception ex) + { + _logger.LogError(ex, "转换子任务失败: {ChildTaskName}, 父任务: {ParentTaskName}", + childData.Name, taskData.Name); + + // 创建错误任务占位符 + var errorTask = new ErrorGearTask(ex.Message) + { + Name = childData.Name, + Type = "error", + Enabled = false, + Father = task + }; + task.Children.Add(errorTask); + } + } + } + + return task; + } + catch (Exception ex) + { + _logger.LogError(ex, "转换任务数据失败: {TaskName}", taskData.Name); + + // 返回错误任务 + return new ErrorGearTask(ex.Message) + { + Name = taskData.Name, + Type = "error", + Enabled = false, + Father = parent + }; + } + } + + /// + /// 验证任务数据的完整性 + /// + /// 任务数据 + /// 验证结果 + public TaskValidationResult ValidateTaskData(GearTaskData taskData) + { + var result = new TaskValidationResult { IsValid = true }; + var errors = new List(); + var warnings = new List(); + + if (taskData == null) + { + errors.Add("任务数据不能为空"); + result.IsValid = false; + } + else + { + // 验证基本属性 + if (string.IsNullOrWhiteSpace(taskData.Name)) + { + errors.Add("任务名称不能为空"); + result.IsValid = false; + } + + if (!taskData.IsDirectory && string.IsNullOrWhiteSpace(taskData.TaskType)) + { + errors.Add("非目录任务必须指定类型"); + result.IsValid = false; + } + + // 验证任务类型 + if (!string.IsNullOrWhiteSpace(taskData.TaskType) && + !GearTaskFactory.IsTaskTypeSupported(taskData.TaskType)) + { + errors.Add($"不支持的任务类型: {taskData.TaskType}"); + result.IsValid = false; + } + + // 验证参数 + if (!taskData.IsDirectory && taskData.IsEnabled) + { + try + { + ValidateTaskParameters(taskData); + } + catch (Exception ex) + { + warnings.Add($"参数验证警告: {ex.Message}"); + } + } + + // 递归验证子任务 + if (taskData.Children?.Count > 0) + { + foreach (var child in taskData.Children) + { + var childResult = ValidateTaskData(child); + if (!childResult.IsValid) + { + errors.AddRange(childResult.Errors.Select(e => $"子任务 '{child.Name}': {e}")); + result.IsValid = false; + } + warnings.AddRange(childResult.Warnings.Select(w => $"子任务 '{child.Name}': {w}")); + } + } + } + + result.Errors = errors; + result.Warnings = warnings; + return result; + } + + /// + /// 验证任务参数 + /// + /// 任务数据 + private void ValidateTaskParameters(GearTaskData taskData) + { + if (taskData.Parameters == null) + { + return; + } + + // 根据任务类型验证参数 + switch (taskData.TaskType?.ToLowerInvariant()) + { + case "javascript": + ValidateJsonParameter(taskData.Parameters, "FolderName"); + break; + case "pathing": + ValidateJsonParameter(taskData.Parameters, "Path"); + break; + case "csharp": + case "csharpreflection": + ValidateJsonParameter(taskData.Parameters, "MethodPath"); + break; + } + } + + /// + /// 验证 JSON 参数中的必需字段 + /// + private void ValidateJsonParameter(object parameters, string requiredField) + { + try + { + string json = parameters is string str ? str : JsonConvert.SerializeObject(parameters); + var obj = JsonConvert.DeserializeObject(json); + + var property = typeof(T).GetProperty(requiredField); + if (property != null) + { + var value = property.GetValue(obj); + if (value == null || (value is string strValue && string.IsNullOrWhiteSpace(strValue))) + { + throw new ArgumentException($"缺少必需参数: {requiredField}"); + } + } + } + catch (JsonException ex) + { + throw new ArgumentException($"参数 JSON 格式错误: {ex.Message}"); + } + } + + /// + /// 统计任务总数(包括子任务) + /// + /// 根任务 + /// 任务总数 + private int CountTotalTasks(BaseGearTask task) + { + int count = 1; + if (task.Children?.Count > 0) + { + count += task.Children.Sum(CountTotalTasks); + } + return count; + } +} + +/// +/// 任务验证结果 +/// +public class TaskValidationResult +{ + public bool IsValid { get; set; } + public List Errors { get; set; } = new(); + public List Warnings { get; set; } = new(); +} + +/// +/// 容器任务,用于目录类型或禁用的任务 +/// +internal class ContainerGearTask : BaseGearTask +{ + public override async Task Run(CancellationToken ct) + { + // 容器任务本身不执行任何操作,只是作为子任务的容器 + await Task.CompletedTask; + } +} + +/// +/// 错误任务,用于转换失败的任务占位符 +/// +internal class ErrorGearTask : BaseGearTask +{ + private readonly string _errorMessage; + + public ErrorGearTask(string errorMessage) + { + _errorMessage = errorMessage; + } + + public override async Task Run(CancellationToken ct) + { + throw new InvalidOperationException($"任务转换失败: {_errorMessage}"); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/GearTask/GearTaskExecutionManager.cs b/BetterGenshinImpact/Service/GearTask/GearTaskExecutionManager.cs new file mode 100644 index 00000000..dcde6029 --- /dev/null +++ b/BetterGenshinImpact/Service/GearTask/GearTaskExecutionManager.cs @@ -0,0 +1,487 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using BetterGenshinImpact.Model.Gear.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.Extensions.Logging; + +namespace BetterGenshinImpact.Service; + +/// +/// 任务执行状态 +/// +public enum TaskExecutionStatus +{ + /// + /// 等待执行 + /// + Pending, + + /// + /// 正在执行 + /// + Running, + + /// + /// 执行完成 + /// + Completed, + + /// + /// 执行失败 + /// + Failed, + + /// + /// 已取消 + /// + Cancelled, + + /// + /// 已跳过 + /// + Skipped +} + +/// +/// 任务执行信息 +/// +public partial class TaskExecutionInfo : ObservableObject +{ + [ObservableProperty] + private string _taskName = string.Empty; + + [ObservableProperty] + private string _taskType = string.Empty; + + [ObservableProperty] + private TaskExecutionStatus _status = TaskExecutionStatus.Pending; + + [ObservableProperty] + private DateTime _startTime; + + [ObservableProperty] + private DateTime _endTime; + + [ObservableProperty] + private TimeSpan _duration; + + [ObservableProperty] + private string _errorMessage = string.Empty; + + [ObservableProperty] + private double _progress; + + [ObservableProperty] + private string _statusMessage = string.Empty; + + public BaseGearTask? Task { get; set; } + public List Children { get; set; } = new(); + public TaskExecutionInfo? Parent { get; set; } +} + +/// +/// 齿轮任务执行管理器,负责管理任务执行状态和进度跟踪 +/// +public partial class GearTaskExecutionManager : ObservableObject +{ + private readonly ILogger _logger; + private readonly Dictionary _taskInfoMap = new(); + private CancellationTokenSource? _cancellationTokenSource; + + [ObservableProperty] + private TaskExecutionInfo? _rootTaskInfo; + + [ObservableProperty] + private TaskExecutionInfo? _currentTaskInfo; + + [ObservableProperty] + private bool _isExecuting; + + [ObservableProperty] + private double _overallProgress; + + [ObservableProperty] + private string _overallStatusMessage = string.Empty; + + [ObservableProperty] + private int _totalTasks; + + [ObservableProperty] + private int _completedTasks; + + [ObservableProperty] + private int _failedTasks; + + [ObservableProperty] + private int _skippedTasks; + + public event EventHandler? TaskStarted; + public event EventHandler? TaskCompleted; + public event EventHandler? TaskFailed; + public event EventHandler? TaskSkipped; + public event EventHandler? ProgressChanged; + + public GearTaskExecutionManager(ILogger logger) + { + _logger = logger; + } + + /// + /// 开始执行任务并跟踪状态 + /// + /// 根任务 + /// 取消令牌 + /// + public async Task ExecuteWithTrackingAsync(BaseGearTask rootTask, CancellationToken ct = default) + { + if (IsExecuting) + { + throw new InvalidOperationException("任务执行管理器正在运行中"); + } + + try + { + IsExecuting = true; + _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(ct); + + // 初始化执行信息 + InitializeExecutionInfo(rootTask); + + // 开始执行 + await ExecuteTaskWithTrackingAsync(rootTask, _cancellationTokenSource.Token); + + // 更新最终状态 + UpdateOverallStatus(); + } + catch (OperationCanceledException) + { + _logger.LogInformation("任务执行已取消"); + OverallStatusMessage = "任务执行已取消"; + throw; + } + catch (Exception ex) + { + _logger.LogError(ex, "任务执行过程中发生错误"); + OverallStatusMessage = $"任务执行失败: {ex.Message}"; + throw; + } + finally + { + IsExecuting = false; + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + } + } + + /// + /// 取消任务执行 + /// + public void CancelExecution() + { + if (IsExecuting && _cancellationTokenSource != null) + { + _logger.LogInformation("用户请求取消任务执行"); + _cancellationTokenSource.Cancel(); + OverallStatusMessage = "正在取消任务执行..."; + } + } + + /// + /// 初始化执行信息 + /// + /// 根任务 + private void InitializeExecutionInfo(BaseGearTask rootTask) + { + _taskInfoMap.Clear(); + CompletedTasks = 0; + FailedTasks = 0; + SkippedTasks = 0; + OverallProgress = 0; + + RootTaskInfo = CreateTaskExecutionInfo(rootTask); + TotalTasks = CountTotalTasks(rootTask); + + OverallStatusMessage = "准备执行任务..."; + + _logger.LogInformation("初始化任务执行跟踪,总任务数: {TotalTasks}", TotalTasks); + } + + /// + /// 创建任务执行信息 + /// + /// 任务 + /// 父任务信息 + /// + private TaskExecutionInfo CreateTaskExecutionInfo(BaseGearTask task, TaskExecutionInfo? parent = null) + { + var info = new TaskExecutionInfo + { + TaskName = task.Name, + TaskType = task.Type, + Status = TaskExecutionStatus.Pending, + Task = task, + Parent = parent + }; + + _taskInfoMap[task] = info; + + // 递归创建子任务信息 + if (task.Children?.Count > 0) + { + foreach (var child in task.Children) + { + var childInfo = CreateTaskExecutionInfo(child, info); + info.Children.Add(childInfo); + } + } + + return info; + } + + /// + /// 执行任务并跟踪状态 + /// + /// 要执行的任务 + /// 取消令牌 + /// + private async Task ExecuteTaskWithTrackingAsync(BaseGearTask task, CancellationToken ct) + { + if (!_taskInfoMap.TryGetValue(task, out var taskInfo)) + { + _logger.LogWarning("未找到任务执行信息: {TaskName}", task.Name); + return; + } + + CurrentTaskInfo = taskInfo; + + // 检查任务是否启用 + if (!task.Enabled) + { + await MarkTaskSkipped(taskInfo, "任务已禁用"); + return; + } + + try + { + // 开始执行任务 + await StartTask(taskInfo); + + // 执行当前任务 + await task.Execute(ct); + + // 执行子任务 + if (task.Children?.Count > 0) + { + for (int i = 0; i < task.Children.Count; i++) + { + var child = task.Children[i]; + await ExecuteTaskWithTrackingAsync(child, ct); + + // 更新进度 + var childProgress = (double)(i + 1) / task.Children.Count * 100; + taskInfo.Progress = childProgress; + UpdateOverallProgress(); + } + } + + // 完成任务 + await CompleteTask(taskInfo); + } + catch (OperationCanceledException) + { + await MarkTaskCancelled(taskInfo); + throw; + } + catch (Exception ex) + { + await FailTask(taskInfo, ex); + throw; + } + } + + /// + /// 开始执行任务 + /// + /// 任务信息 + private async Task StartTask(TaskExecutionInfo taskInfo) + { + taskInfo.Status = TaskExecutionStatus.Running; + taskInfo.StartTime = DateTime.Now; + taskInfo.StatusMessage = "正在执行..."; + + _logger.LogInformation("开始执行任务: {TaskName}", taskInfo.TaskName); + + TaskStarted?.Invoke(this, taskInfo); + await Task.CompletedTask; + } + + /// + /// 完成任务 + /// + /// 任务信息 + private async Task CompleteTask(TaskExecutionInfo taskInfo) + { + taskInfo.Status = TaskExecutionStatus.Completed; + taskInfo.EndTime = DateTime.Now; + taskInfo.Duration = taskInfo.EndTime - taskInfo.StartTime; + taskInfo.Progress = 100; + taskInfo.StatusMessage = "执行完成"; + + CompletedTasks++; + + _logger.LogInformation("任务执行完成: {TaskName}, 耗时: {Duration}ms", + taskInfo.TaskName, taskInfo.Duration.TotalMilliseconds); + + TaskCompleted?.Invoke(this, taskInfo); + UpdateOverallProgress(); + + await Task.CompletedTask; + } + + /// + /// 任务执行失败 + /// + /// 任务信息 + /// 异常 + private async Task FailTask(TaskExecutionInfo taskInfo, Exception exception) + { + taskInfo.Status = TaskExecutionStatus.Failed; + taskInfo.EndTime = DateTime.Now; + taskInfo.Duration = taskInfo.EndTime - taskInfo.StartTime; + taskInfo.ErrorMessage = exception.Message; + taskInfo.StatusMessage = $"执行失败: {exception.Message}"; + + FailedTasks++; + + _logger.LogError(exception, "任务执行失败: {TaskName}", taskInfo.TaskName); + + TaskFailed?.Invoke(this, taskInfo); + UpdateOverallProgress(); + + await Task.CompletedTask; + } + + /// + /// 标记任务为已跳过 + /// + /// 任务信息 + /// 跳过原因 + private async Task MarkTaskSkipped(TaskExecutionInfo taskInfo, string reason) + { + taskInfo.Status = TaskExecutionStatus.Skipped; + taskInfo.StatusMessage = $"已跳过: {reason}"; + + SkippedTasks++; + + _logger.LogInformation("任务已跳过: {TaskName}, 原因: {Reason}", taskInfo.TaskName, reason); + + TaskSkipped?.Invoke(this, taskInfo); + UpdateOverallProgress(); + + await Task.CompletedTask; + } + + /// + /// 标记任务为已取消 + /// + /// 任务信息 + private async Task MarkTaskCancelled(TaskExecutionInfo taskInfo) + { + taskInfo.Status = TaskExecutionStatus.Cancelled; + taskInfo.EndTime = DateTime.Now; + taskInfo.Duration = taskInfo.EndTime - taskInfo.StartTime; + taskInfo.StatusMessage = "已取消"; + + _logger.LogInformation("任务已取消: {TaskName}", taskInfo.TaskName); + + await Task.CompletedTask; + } + + /// + /// 更新整体进度 + /// + private void UpdateOverallProgress() + { + if (TotalTasks > 0) + { + var processedTasks = CompletedTasks + FailedTasks + SkippedTasks; + OverallProgress = (double)processedTasks / TotalTasks * 100; + + OverallStatusMessage = $"进度: {processedTasks}/{TotalTasks} (完成: {CompletedTasks}, 失败: {FailedTasks}, 跳过: {SkippedTasks})"; + + ProgressChanged?.Invoke(this, OverallProgress); + } + } + + /// + /// 更新最终状态 + /// + private void UpdateOverallStatus() + { + if (FailedTasks > 0) + { + OverallStatusMessage = $"执行完成,但有 {FailedTasks} 个任务失败"; + } + else if (SkippedTasks > 0) + { + OverallStatusMessage = $"执行完成,跳过了 {SkippedTasks} 个任务"; + } + else + { + OverallStatusMessage = "所有任务执行完成"; + } + } + + /// + /// 统计任务总数 + /// + /// 根任务 + /// 任务总数 + private int CountTotalTasks(BaseGearTask task) + { + int count = 1; + if (task.Children?.Count > 0) + { + foreach (var child in task.Children) + { + count += CountTotalTasks(child); + } + } + return count; + } + + /// + /// 获取任务执行统计信息 + /// + /// 统计信息 + public TaskExecutionStatistics GetStatistics() + { + return new TaskExecutionStatistics + { + TotalTasks = TotalTasks, + CompletedTasks = CompletedTasks, + FailedTasks = FailedTasks, + SkippedTasks = SkippedTasks, + OverallProgress = OverallProgress, + IsExecuting = IsExecuting + }; + } +} + +/// +/// 任务执行统计信息 +/// +public class TaskExecutionStatistics +{ + public int TotalTasks { get; set; } + public int CompletedTasks { get; set; } + public int FailedTasks { get; set; } + public int SkippedTasks { get; set; } + public double OverallProgress { get; set; } + public bool IsExecuting { get; set; } + + public int ProcessedTasks => CompletedTasks + FailedTasks + SkippedTasks; + public double SuccessRate => TotalTasks > 0 ? (double)CompletedTasks / TotalTasks * 100 : 0; +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/GearTask/GearTaskExecutor.cs b/BetterGenshinImpact/Service/GearTask/GearTaskExecutor.cs new file mode 100644 index 00000000..3bbcf3fc --- /dev/null +++ b/BetterGenshinImpact/Service/GearTask/GearTaskExecutor.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; +using BetterGenshinImpact.Model.Gear; +using BetterGenshinImpact.Model.Gear.Tasks; +using BetterGenshinImpact.ViewModel.Pages.Component; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace BetterGenshinImpact.Service; + +/// +/// 齿轮任务执行器,负责从 JSON 数据解析任务并执行 +/// +public partial class GearTaskExecutor : ObservableObject +{ + private readonly ILogger _logger; + private readonly GearTaskStorageService _storageService; + private readonly GearTaskConverter _taskConverter; + private readonly GearTaskExecutionManager _executionManager; + private readonly IServiceProvider _serviceProvider; + + [ObservableProperty] + private bool _isExecuting; + + [ObservableProperty] + private string _currentTaskName = string.Empty; + + [ObservableProperty] + private double _progress; + + [ObservableProperty] + private string _statusMessage = string.Empty; + + public GearTaskExecutor( + ILogger logger, + GearTaskStorageService storageService, + GearTaskConverter taskConverter, + GearTaskExecutionManager executionManager, + IServiceProvider serviceProvider) + { + _logger = logger; + _storageService = storageService; + _taskConverter = taskConverter; + _executionManager = executionManager; + _serviceProvider = serviceProvider; + + // 订阅执行管理器事件 + _executionManager.ProgressChanged += OnProgressChanged; + _executionManager.PropertyChanged += OnExecutionManagerPropertyChanged; + } + + /// + /// 从 JSON 文件加载并执行任务定义 + /// + /// 任务定义名称 + /// 取消令牌 + /// + public async Task ExecuteTaskDefinitionAsync(string taskDefinitionName, CancellationToken ct = default) + { + if (IsExecuting) + { + throw new InvalidOperationException("任务执行器正在运行中,请等待当前任务完成"); + } + + try + { + IsExecuting = true; + StatusMessage = "正在加载任务定义..."; + Progress = 0; + + // 从存储服务加载任务定义 + var taskDefinitionViewModel = await _storageService.LoadTaskDefinitionAsync(taskDefinitionName); + if (taskDefinitionViewModel == null) + { + throw new ArgumentException($"未找到任务定义: {taskDefinitionName}"); + } + + if (taskDefinitionViewModel.RootTask == null) + { + throw new InvalidOperationException($"任务定义 '{taskDefinitionName}' 没有根任务"); + } + + _logger.LogInformation("开始执行任务定义: {TaskDefinitionName}", taskDefinitionName); + + // 转换为可执行的任务 + var rootTaskData = ConvertViewModelToData(taskDefinitionViewModel.RootTask); + var rootTask = await _taskConverter.ConvertTaskDataAsync(rootTaskData); + + // 使用执行管理器执行任务 + await _executionManager.ExecuteWithTrackingAsync(rootTask, ct); + + StatusMessage = "任务执行完成"; + Progress = 100; + _logger.LogInformation("任务定义执行完成: {TaskDefinitionName}", taskDefinitionName); + } + catch (OperationCanceledException) + { + StatusMessage = "任务执行已取消"; + _logger.LogInformation("任务定义执行已取消: {TaskDefinitionName}", taskDefinitionName); + throw; + } + catch (Exception ex) + { + StatusMessage = $"任务执行失败: {ex.Message}"; + _logger.LogError(ex, "执行任务定义时发生错误: {TaskDefinitionName}", taskDefinitionName); + throw; + } + finally + { + IsExecuting = false; + } + } + + /// + /// 直接执行 GearTaskData + /// + /// 任务数据 + /// 取消令牌 + /// + public async Task ExecuteTaskDataAsync(GearTaskData taskData, CancellationToken ct = default) + { + if (IsExecuting) + { + throw new InvalidOperationException("任务执行器正在运行中,请等待当前任务完成"); + } + + try + { + IsExecuting = true; + StatusMessage = "正在执行任务..."; + Progress = 0; + + _logger.LogInformation("开始执行任务: {TaskName}", taskData.Name); + + // 转换为可执行的任务 + var executableTask = await _taskConverter.ConvertTaskDataAsync(taskData); + + // 使用执行管理器执行任务 + await _executionManager.ExecuteWithTrackingAsync(executableTask, ct); + + StatusMessage = "任务执行完成"; + Progress = 100; + _logger.LogInformation("任务执行完成: {TaskName}", taskData.Name); + } + catch (OperationCanceledException) + { + StatusMessage = "任务执行已取消"; + _logger.LogInformation("任务执行已取消: {TaskName}", taskData.Name); + throw; + } + catch (Exception ex) + { + StatusMessage = $"任务执行失败: {ex.Message}"; + _logger.LogError(ex, "执行任务时发生错误: {TaskName}", taskData.Name); + throw; + } + finally + { + IsExecuting = false; + } + } + + + + + + /// + /// 停止当前执行的任务 + /// + public void StopExecution() + { + if (IsExecuting) + { + StatusMessage = "正在停止任务执行..."; + _logger.LogInformation("用户请求停止任务执行"); + _executionManager.CancelExecution(); + } + } + + /// + /// 处理进度变化事件 + /// + /// 事件发送者 + /// 进度值 + private void OnProgressChanged(object? sender, double progress) + { + Progress = progress; + } + + /// + /// 处理执行管理器属性变化事件 + /// + /// 事件发送者 + /// 属性变化事件参数 + private void OnExecutionManagerPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(GearTaskExecutionManager.CurrentTaskInfo): + if (_executionManager.CurrentTaskInfo != null) + { + CurrentTaskName = _executionManager.CurrentTaskInfo.TaskName; + } + break; + case nameof(GearTaskExecutionManager.OverallStatusMessage): + StatusMessage = _executionManager.OverallStatusMessage; + break; + case nameof(GearTaskExecutionManager.IsExecuting): + IsExecuting = _executionManager.IsExecuting; + break; + } + } + + /// + /// 获取执行统计信息 + /// + /// 统计信息 + public TaskExecutionStatistics GetExecutionStatistics() + { + return _executionManager.GetStatistics(); + } + + /// + /// 获取当前任务执行信息 + /// + /// 当前任务信息 + public TaskExecutionInfo? GetCurrentTaskInfo() + { + return _executionManager.CurrentTaskInfo; + } + + /// + /// 获取根任务执行信息 + /// + /// + public TaskExecutionInfo? GetRootTaskInfo() + { + return _executionManager.RootTaskInfo; + } + + /// + /// 将 GearTaskViewModel 转换为 GearTaskData + /// + /// 视图模型 + /// 任务数据 + private GearTaskData ConvertViewModelToData(GearTaskViewModel viewModel) + { + var taskData = new GearTaskData + { + Name = viewModel.Name, + Description = viewModel.Description, + TaskType = viewModel.TaskType, + IsEnabled = viewModel.IsEnabled, + IsDirectory = viewModel.IsDirectory, + Parameters = viewModel.Parameters, + CreatedTime = viewModel.CreatedTime, + ModifiedTime = viewModel.ModifiedTime, + Priority = viewModel.Priority, + Tags = viewModel.Tags ?? string.Empty + }; + + // 递归转换子任务 + if (viewModel.Children?.Count > 0) + { + taskData.Children = new List(); + foreach (var child in viewModel.Children) + { + taskData.Children.Add(ConvertViewModelToData(child)); + } + } + + return taskData; + } +} + +/// +/// 空任务实现,用于已禁用的任务 +/// +internal class EmptyGearTask : BaseGearTask +{ + public override Task Run(CancellationToken ct) + { + return Task.CompletedTask; + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/GearTask/GearTaskExecutor_Usage.md b/BetterGenshinImpact/Service/GearTask/GearTaskExecutor_Usage.md new file mode 100644 index 00000000..3fb2004e --- /dev/null +++ b/BetterGenshinImpact/Service/GearTask/GearTaskExecutor_Usage.md @@ -0,0 +1,329 @@ +# GearTaskExecutor 使用指南 + +## 概述 + +`GearTaskExecutor` 是一个强大的任务执行控制器,能够从 JSON 文件中解析并执行继承自 `BaseGearTask` 的不同类型任务。这些任务统一使用 `GearTaskData` 对象进行序列化。 + +## 核心组件 + +### 1. GearTaskExecutor +主要的任务执行控制器,提供以下功能: +- 从 JSON 文件加载任务定义 +- 执行任务并跟踪进度 +- 异常处理和日志记录 +- 任务状态管理 + +### 2. GearTaskFactory +任务工厂,根据任务类型创建对应的任务实例: +- JavaScript 任务 +- Pathing 任务 +- C# 反射任务 +- KeyMouse 任务 +- Shell 任务 + +### 3. GearTaskConverter +任务转换器,将 `GearTaskData` 转换为可执行的 `BaseGearTask`: +- 递归处理子任务 +- 参数验证 +- 错误处理 + +### 4. GearTaskExecutionManager +任务执行管理器,提供详细的执行状态跟踪: +- 实时进度更新 +- 任务状态监控 +- 执行统计信息 +- 事件通知 + +## 服务注册 + +在 `App.xaml.cs` 中注册服务: + +```csharp +// 方式1:使用扩展方法注册所有服务 +services.AddGearTaskServices(); + +// 方式2:带配置选项注册 +services.AddGearTaskServices(options => +{ + options.EnableVerboseLogging = true; + options.ContinueOnTaskFailure = false; + options.MaxConcurrentTasks = 1; +}); + +// 方式3:手动注册(如果需要自定义) +services.AddSingleton(); +services.AddSingleton(); +services.AddSingleton(); +services.AddTransient(); +services.AddTransient(); +``` + +## 基本使用 + +### 1. 执行任务定义 + +```csharp +public class TaskExecutionViewModel : ObservableObject +{ + private readonly GearTaskExecutor _taskExecutor; + + public TaskExecutionViewModel(GearTaskExecutor taskExecutor) + { + _taskExecutor = taskExecutor; + } + + [RelayCommand] + private async Task ExecuteTaskDefinitionAsync(string taskDefinitionName) + { + try + { + await _taskExecutor.ExecuteTaskDefinitionAsync(taskDefinitionName); + } + catch (Exception ex) + { + // 处理异常 + Debug.WriteLine($"任务执行失败: {ex.Message}"); + } + } +} +``` + +### 2. 直接执行任务数据 + +```csharp +[RelayCommand] +private async Task ExecuteTaskDataAsync() +{ + var taskData = new GearTaskData + { + Name = "测试任务", + Type = "javascript", + IsEnabled = true, + Parameters = new JavascriptGearTaskParams + { + FolderName = "test-script", + Context = new { message = "Hello World" } + } + }; + + try + { + await _taskExecutor.ExecuteTaskDataAsync(taskData); + } + catch (Exception ex) + { + // 处理异常 + } +} +``` + +### 3. 监控执行进度 + +```csharp +public class TaskMonitorViewModel : ObservableObject +{ + private readonly GearTaskExecutor _taskExecutor; + + [ObservableProperty] + private double _progress; + + [ObservableProperty] + private string _currentTaskName = string.Empty; + + [ObservableProperty] + private string _statusMessage = string.Empty; + + public TaskMonitorViewModel(GearTaskExecutor taskExecutor) + { + _taskExecutor = taskExecutor; + + // 绑定属性 + _taskExecutor.PropertyChanged += OnTaskExecutorPropertyChanged; + } + + private void OnTaskExecutorPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(GearTaskExecutor.Progress): + Progress = _taskExecutor.Progress; + break; + case nameof(GearTaskExecutor.CurrentTaskName): + CurrentTaskName = _taskExecutor.CurrentTaskName; + break; + case nameof(GearTaskExecutor.StatusMessage): + StatusMessage = _taskExecutor.StatusMessage; + break; + } + } + + [RelayCommand] + private void StopExecution() + { + _taskExecutor.StopExecution(); + } +} +``` + +### 4. 获取执行统计信息 + +```csharp +[RelayCommand] +private void ShowStatistics() +{ + var stats = _taskExecutor.GetExecutionStatistics(); + + var message = $@"执行统计信息: +总任务数: {stats.TotalTasks} +已完成: {stats.CompletedTasks} +失败: {stats.FailedTasks} +跳过: {stats.SkippedTasks} +成功率: {stats.SuccessRate:F1}% +整体进度: {stats.OverallProgress:F1}%"; + + MessageBox.Show(message); +} +``` + +## 支持的任务类型 + +### 1. JavaScript 任务 +```json +{ + "name": "JavaScript 脚本任务", + "type": "javascript", + "isEnabled": true, + "parameters": { + "folderName": "my-script", + "context": { + "param1": "value1", + "param2": 123 + } + } +} +``` + +### 2. Pathing 任务 +```json +{ + "name": "路径任务", + "type": "pathing", + "isEnabled": true, + "parameters": { + "path": "path/to/route.json", + "pathingPartyConfig": { + "team": ["character1", "character2"] + } + } +} +``` + +### 3. C# 反射任务 +```json +{ + "name": "C# 反射任务", + "type": "csharp", + "isEnabled": true, + "parameters": { + "methodPath": "MyNamespace.MyClass.MyMethod", + "parametersJson": "[\"param1\", 123, {\"key\": \"value\"}]" + } +} +``` + +### 4. KeyMouse 任务 +```json +{ + "name": "键鼠录制任务", + "type": "keymouse", + "isEnabled": true, + "parameters": "path/to/macro.json" +} +``` + +### 5. Shell 任务 +```json +{ + "name": "Shell 命令任务", + "type": "shell", + "isEnabled": true, + "parameters": { + "command": "echo Hello World", + "workingDirectory": "C:\\temp", + "timeout": 30000 + } +} +``` + +## 错误处理 + +### 1. 任务验证 +```csharp +public async Task ValidateTaskAsync(GearTaskData taskData) +{ + var converter = serviceProvider.GetRequiredService(); + var result = converter.ValidateTaskData(taskData); + + if (!result.IsValid) + { + foreach (var error in result.Errors) + { + Debug.WriteLine($"验证错误: {error}"); + } + return false; + } + + foreach (var warning in result.Warnings) + { + Debug.WriteLine($"验证警告: {warning}"); + } + + return true; +} +``` + +### 2. 异常处理 +```csharp +try +{ + await _taskExecutor.ExecuteTaskDefinitionAsync(taskName); +} +catch (ArgumentException ex) +{ + // 参数错误 + MessageBox.Show($"参数错误: {ex.Message}"); +} +catch (FileNotFoundException ex) +{ + // 文件未找到 + MessageBox.Show($"文件未找到: {ex.Message}"); +} +catch (OperationCanceledException) +{ + // 用户取消 + MessageBox.Show("任务执行已取消"); +} +catch (Exception ex) +{ + // 其他异常 + MessageBox.Show($"执行失败: {ex.Message}"); +} +``` + +## 最佳实践 + +1. **使用依赖注入**:通过构造函数注入 `GearTaskExecutor` +2. **异步执行**:所有任务执行都应该使用 `async/await` +3. **异常处理**:始终包装任务执行在 try-catch 块中 +4. **进度监控**:绑定执行器的属性来显示进度 +5. **资源清理**:在适当的时候取消任务执行 +6. **日志记录**:启用详细日志记录来调试问题 +7. **参数验证**:在执行前验证任务数据的完整性 + +## 注意事项 + +- 任务执行器是线程安全的,但同时只能执行一个任务 +- 子任务会按顺序执行,不支持并行执行 +- 禁用的任务会被跳过,但仍会处理其子任务 +- 任务失败会停止整个执行流程,除非配置为继续执行 +- 所有任务都支持取消操作 \ No newline at end of file diff --git a/BetterGenshinImpact/Service/GearTask/GearTaskFactory.cs b/BetterGenshinImpact/Service/GearTask/GearTaskFactory.cs new file mode 100644 index 00000000..f3390e5a --- /dev/null +++ b/BetterGenshinImpact/Service/GearTask/GearTaskFactory.cs @@ -0,0 +1,251 @@ +using System; +using System.Threading.Tasks; +using BetterGenshinImpact.Model.Gear; +using BetterGenshinImpact.Model.Gear.Tasks; +using BetterGenshinImpact.Model.Gear.Parameter; +using BetterGenshinImpact.Core.Config; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using System.IO; + +namespace BetterGenshinImpact.Service; + +/// +/// 齿轮任务工厂,根据任务类型和参数创建对应的任务实例 +/// +public class GearTaskFactory +{ + private readonly ILogger _logger; + private readonly IServiceProvider _serviceProvider; + + public GearTaskFactory(ILogger logger, IServiceProvider serviceProvider) + { + _logger = logger; + _serviceProvider = serviceProvider; + } + + /// + /// 根据 GearTaskData 创建对应的任务实例 + /// + /// 任务数据 + /// 创建的任务实例 + public async Task CreateTaskAsync(GearTaskData taskData) + { + if (string.IsNullOrWhiteSpace(taskData.TaskType)) + { + throw new ArgumentException($"任务类型不能为空: {taskData.Name}"); + } + + try + { + var task = taskData.TaskType.ToLowerInvariant() switch + { + "javascript" => await CreateJavascriptTaskAsync(taskData), + "pathing" => await CreatePathingTaskAsync(taskData), + "csharp" or "csharpreflection" => await CreateCSharpReflectionTaskAsync(taskData), + "keymouse" => await CreateKeyMouseTaskAsync(taskData), + "shell" => await CreateShellTaskAsync(taskData), + _ => throw new NotSupportedException($"不支持的任务类型: {taskData.TaskType}") + }; + + // 设置基本属性 + task.Name = taskData.Name; + task.Type = taskData.TaskType; + task.Enabled = taskData.IsEnabled; + + _logger.LogDebug("成功创建任务实例: {TaskName} ({TaskType})", taskData.Name, taskData.TaskType); + return task; + } + catch (Exception ex) + { + _logger.LogError(ex, "创建任务实例失败: {TaskName} ({TaskType})", taskData.Name, taskData.TaskType); + throw; + } + } + + /// + /// 创建 JavaScript 任务 + /// + private async Task CreateJavascriptTaskAsync(GearTaskData taskData) + { + var parameters = DeserializeParameters(taskData.Parameters); + + if (string.IsNullOrWhiteSpace(parameters.FolderName)) + { + throw new ArgumentException($"JavaScript 任务缺少 FolderName 参数: {taskData.Name}"); + } + + return new JavascriptGearTask(parameters); + } + + /// + /// 创建路径任务 + /// + private async Task CreatePathingTaskAsync(GearTaskData taskData) + { + var parameters = DeserializeParameters(taskData.Parameters); + + if (string.IsNullOrWhiteSpace(parameters.Path)) + { + throw new ArgumentException($"Pathing 任务缺少 Path 参数: {taskData.Name}"); + } + + return new PathingGearTask(parameters); + } + + /// + /// 创建 C# 反射任务 + /// + private async Task CreateCSharpReflectionTaskAsync(GearTaskData taskData) + { + var parameters = DeserializeParameters(taskData.Parameters); + + if (string.IsNullOrWhiteSpace(parameters.MethodPath)) + { + throw new ArgumentException($"C# 反射任务缺少 MethodPath 参数: {taskData.Name}"); + } + + return new CSharpReflectionGearTask(parameters); + } + + /// + /// 创建键鼠任务 + /// + private async Task CreateKeyMouseTaskAsync(GearTaskData taskData) + { + // KeyMouse 任务需要文件路径 + string filePath = string.Empty; + + if (taskData.Parameters != null) + { + // 尝试从参数中获取路径 + if (taskData.Parameters is string pathStr) + { + filePath = pathStr; + } + else if (taskData.Parameters.ToString() is string paramStr) + { + // 尝试解析 JSON 对象中的路径 + try + { + var paramObj = JsonConvert.DeserializeObject(paramStr); + filePath = paramObj?.Path?.ToString() ?? paramObj?.FilePath?.ToString() ?? string.Empty; + } + catch + { + filePath = paramStr; // 如果解析失败,直接使用字符串 + } + } + } + + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new ArgumentException($"KeyMouse 任务缺少文件路径参数: {taskData.Name}"); + } + + if (!File.Exists(filePath)) + { + throw new FileNotFoundException($"KeyMouse 任务文件不存在: {filePath}"); + } + + return new KeyMouseGearTask(filePath); + } + + /// + /// 创建 Shell 任务 + /// + private async Task CreateShellTaskAsync(GearTaskData taskData) + { + ShellConfig? shellConfig = null; + + if (taskData.Parameters != null) + { + try + { + shellConfig = DeserializeParameters(taskData.Parameters); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "解析 Shell 配置失败,使用默认配置: {TaskName}", taskData.Name); + shellConfig = new ShellConfig(); + } + } + + return new ShellGearTask(shellConfig); + } + + /// + /// 反序列化参数对象 + /// + /// 参数类型 + /// 参数对象 + /// 反序列化后的参数 + private T DeserializeParameters(object? parameters) where T : class, new() + { + if (parameters == null) + { + return new T(); + } + + try + { + // 如果已经是目标类型,直接返回 + if (parameters is T directCast) + { + return directCast; + } + + // 尝试 JSON 反序列化 + string json; + if (parameters is string jsonStr) + { + json = jsonStr; + } + else + { + json = JsonConvert.SerializeObject(parameters); + } + + var result = JsonConvert.DeserializeObject(json); + return result ?? new T(); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "反序列化参数失败,使用默认参数: {ParameterType}", typeof(T).Name); + return new T(); + } + } + + /// + /// 获取支持的任务类型列表 + /// + /// 支持的任务类型 + public static string[] GetSupportedTaskTypes() + { + return new[] + { + "javascript", + "pathing", + "csharp", + "csharpreflection", + "keymouse", + "shell" + }; + } + + /// + /// 检查任务类型是否受支持 + /// + /// 任务类型 + /// 是否支持 + public static bool IsTaskTypeSupported(string taskType) + { + if (string.IsNullOrWhiteSpace(taskType)) + return false; + + var supportedTypes = GetSupportedTaskTypes(); + return Array.Exists(supportedTypes, t => + string.Equals(t, taskType, StringComparison.OrdinalIgnoreCase)); + } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/GearTask/GearTaskServiceExtensions.cs b/BetterGenshinImpact/Service/GearTask/GearTaskServiceExtensions.cs new file mode 100644 index 00000000..a175e316 --- /dev/null +++ b/BetterGenshinImpact/Service/GearTask/GearTaskServiceExtensions.cs @@ -0,0 +1,78 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace BetterGenshinImpact.Service; + +/// +/// 齿轮任务服务注册扩展方法 +/// +public static class GearTaskServiceExtensions +{ + /// + /// 注册齿轮任务相关服务 + /// + /// 服务集合 + /// 服务集合 + public static IServiceCollection AddGearTaskServices(this IServiceCollection services) + { + // 注册核心服务 + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddTransient(); + services.AddTransient(); + + return services; + } + + /// + /// 注册齿轮任务相关服务(带自定义配置) + /// + /// 服务集合 + /// 配置选项 + /// 服务集合 + public static IServiceCollection AddGearTaskServices(this IServiceCollection services, + Action? configureOptions = null) + { + var options = new GearTaskServiceOptions(); + configureOptions?.Invoke(options); + + // 注册配置 + services.AddSingleton(options); + + // 注册核心服务 + return services.AddGearTaskServices(); + } +} + +/// +/// 齿轮任务服务配置选项 +/// +public class GearTaskServiceOptions +{ + /// + /// 是否启用详细日志记录 + /// + public bool EnableVerboseLogging { get; set; } = false; + + /// + /// 任务执行超时时间(毫秒),0 表示无超时 + /// + public int TaskExecutionTimeoutMs { get; set; } = 0; + + /// + /// 是否在任务失败时继续执行后续任务 + /// + public bool ContinueOnTaskFailure { get; set; } = false; + + /// + /// 最大并发任务数,0 表示无限制 + /// + public int MaxConcurrentTasks { get; set; } = 1; + + /// + /// 任务存储路径,null 表示使用默认路径 + /// + public string? TaskStoragePath { get; set; } +} \ No newline at end of file diff --git a/BetterGenshinImpact/Service/GearTaskStorageService.cs b/BetterGenshinImpact/Service/GearTask/GearTaskStorageService.cs similarity index 100% rename from BetterGenshinImpact/Service/GearTaskStorageService.cs rename to BetterGenshinImpact/Service/GearTask/GearTaskStorageService.cs