任务管理与执行

This commit is contained in:
辉鸭蛋
2025-08-27 02:36:35 +08:00
parent 58e3fbb2e4
commit 55f15f6206
8 changed files with 1763 additions and 9 deletions

View File

@@ -1,9 +0,0 @@
namespace BetterGenshinImpact.Model.Gear.Tasks;
/// <summary>
/// 为了和其他Trigger做区分使用Gear(齿轮)来作为前缀命名调度器内定义的触发器
/// </summary>
public class GearTrigger
{
}

View File

@@ -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;
/// <summary>
/// 齿轮任务转换器,负责将 GearTaskData 转换为可执行的 BaseGearTask
/// </summary>
public class GearTaskConverter
{
private readonly ILogger<GearTaskConverter> _logger;
private readonly GearTaskFactory _taskFactory;
public GearTaskConverter(ILogger<GearTaskConverter> logger, GearTaskFactory taskFactory)
{
_logger = logger;
_taskFactory = taskFactory;
}
/// <summary>
/// 将 GearTaskDefinitionData 转换为可执行的任务列表
/// </summary>
/// <param name="taskDefinition">任务定义数据</param>
/// <returns>可执行的任务列表</returns>
public async Task<List<BaseGearTask>> ConvertTaskDefinitionAsync(GearTaskDefinitionData taskDefinition)
{
if (taskDefinition?.RootTask == null)
{
throw new ArgumentException("任务定义或根任务不能为空");
}
var tasks = new List<BaseGearTask>();
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;
}
}
/// <summary>
/// 将单个 GearTaskData 转换为 BaseGearTask包括子任务
/// </summary>
/// <param name="taskData">任务数据</param>
/// <param name="parent">父任务</param>
/// <returns>转换后的任务</returns>
public async Task<BaseGearTask> 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
};
}
}
/// <summary>
/// 验证任务数据的完整性
/// </summary>
/// <param name="taskData">任务数据</param>
/// <returns>验证结果</returns>
public TaskValidationResult ValidateTaskData(GearTaskData taskData)
{
var result = new TaskValidationResult { IsValid = true };
var errors = new List<string>();
var warnings = new List<string>();
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;
}
/// <summary>
/// 验证任务参数
/// </summary>
/// <param name="taskData">任务数据</param>
private void ValidateTaskParameters(GearTaskData taskData)
{
if (taskData.Parameters == null)
{
return;
}
// 根据任务类型验证参数
switch (taskData.TaskType?.ToLowerInvariant())
{
case "javascript":
ValidateJsonParameter<JavascriptGearTaskParams>(taskData.Parameters, "FolderName");
break;
case "pathing":
ValidateJsonParameter<PathingGearTaskParams>(taskData.Parameters, "Path");
break;
case "csharp":
case "csharpreflection":
ValidateJsonParameter<CSharpReflectionGearTaskParams>(taskData.Parameters, "MethodPath");
break;
}
}
/// <summary>
/// 验证 JSON 参数中的必需字段
/// </summary>
private void ValidateJsonParameter<T>(object parameters, string requiredField)
{
try
{
string json = parameters is string str ? str : JsonConvert.SerializeObject(parameters);
var obj = JsonConvert.DeserializeObject<T>(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}");
}
}
/// <summary>
/// 统计任务总数(包括子任务)
/// </summary>
/// <param name="task">根任务</param>
/// <returns>任务总数</returns>
private int CountTotalTasks(BaseGearTask task)
{
int count = 1;
if (task.Children?.Count > 0)
{
count += task.Children.Sum(CountTotalTasks);
}
return count;
}
}
/// <summary>
/// 任务验证结果
/// </summary>
public class TaskValidationResult
{
public bool IsValid { get; set; }
public List<string> Errors { get; set; } = new();
public List<string> Warnings { get; set; } = new();
}
/// <summary>
/// 容器任务,用于目录类型或禁用的任务
/// </summary>
internal class ContainerGearTask : BaseGearTask
{
public override async Task Run(CancellationToken ct)
{
// 容器任务本身不执行任何操作,只是作为子任务的容器
await Task.CompletedTask;
}
}
/// <summary>
/// 错误任务,用于转换失败的任务占位符
/// </summary>
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}");
}
}

View File

@@ -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;
/// <summary>
/// 任务执行状态
/// </summary>
public enum TaskExecutionStatus
{
/// <summary>
/// 等待执行
/// </summary>
Pending,
/// <summary>
/// 正在执行
/// </summary>
Running,
/// <summary>
/// 执行完成
/// </summary>
Completed,
/// <summary>
/// 执行失败
/// </summary>
Failed,
/// <summary>
/// 已取消
/// </summary>
Cancelled,
/// <summary>
/// 已跳过
/// </summary>
Skipped
}
/// <summary>
/// 任务执行信息
/// </summary>
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<TaskExecutionInfo> Children { get; set; } = new();
public TaskExecutionInfo? Parent { get; set; }
}
/// <summary>
/// 齿轮任务执行管理器,负责管理任务执行状态和进度跟踪
/// </summary>
public partial class GearTaskExecutionManager : ObservableObject
{
private readonly ILogger<GearTaskExecutionManager> _logger;
private readonly Dictionary<BaseGearTask, TaskExecutionInfo> _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<TaskExecutionInfo>? TaskStarted;
public event EventHandler<TaskExecutionInfo>? TaskCompleted;
public event EventHandler<TaskExecutionInfo>? TaskFailed;
public event EventHandler<TaskExecutionInfo>? TaskSkipped;
public event EventHandler<double>? ProgressChanged;
public GearTaskExecutionManager(ILogger<GearTaskExecutionManager> logger)
{
_logger = logger;
}
/// <summary>
/// 开始执行任务并跟踪状态
/// </summary>
/// <param name="rootTask">根任务</param>
/// <param name="ct">取消令牌</param>
/// <returns></returns>
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;
}
}
/// <summary>
/// 取消任务执行
/// </summary>
public void CancelExecution()
{
if (IsExecuting && _cancellationTokenSource != null)
{
_logger.LogInformation("用户请求取消任务执行");
_cancellationTokenSource.Cancel();
OverallStatusMessage = "正在取消任务执行...";
}
}
/// <summary>
/// 初始化执行信息
/// </summary>
/// <param name="rootTask">根任务</param>
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);
}
/// <summary>
/// 创建任务执行信息
/// </summary>
/// <param name="task">任务</param>
/// <param name="parent">父任务信息</param>
/// <returns></returns>
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;
}
/// <summary>
/// 执行任务并跟踪状态
/// </summary>
/// <param name="task">要执行的任务</param>
/// <param name="ct">取消令牌</param>
/// <returns></returns>
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;
}
}
/// <summary>
/// 开始执行任务
/// </summary>
/// <param name="taskInfo">任务信息</param>
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;
}
/// <summary>
/// 完成任务
/// </summary>
/// <param name="taskInfo">任务信息</param>
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;
}
/// <summary>
/// 任务执行失败
/// </summary>
/// <param name="taskInfo">任务信息</param>
/// <param name="exception">异常</param>
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;
}
/// <summary>
/// 标记任务为已跳过
/// </summary>
/// <param name="taskInfo">任务信息</param>
/// <param name="reason">跳过原因</param>
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;
}
/// <summary>
/// 标记任务为已取消
/// </summary>
/// <param name="taskInfo">任务信息</param>
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;
}
/// <summary>
/// 更新整体进度
/// </summary>
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);
}
}
/// <summary>
/// 更新最终状态
/// </summary>
private void UpdateOverallStatus()
{
if (FailedTasks > 0)
{
OverallStatusMessage = $"执行完成,但有 {FailedTasks} 个任务失败";
}
else if (SkippedTasks > 0)
{
OverallStatusMessage = $"执行完成,跳过了 {SkippedTasks} 个任务";
}
else
{
OverallStatusMessage = "所有任务执行完成";
}
}
/// <summary>
/// 统计任务总数
/// </summary>
/// <param name="task">根任务</param>
/// <returns>任务总数</returns>
private int CountTotalTasks(BaseGearTask task)
{
int count = 1;
if (task.Children?.Count > 0)
{
foreach (var child in task.Children)
{
count += CountTotalTasks(child);
}
}
return count;
}
/// <summary>
/// 获取任务执行统计信息
/// </summary>
/// <returns>统计信息</returns>
public TaskExecutionStatistics GetStatistics()
{
return new TaskExecutionStatistics
{
TotalTasks = TotalTasks,
CompletedTasks = CompletedTasks,
FailedTasks = FailedTasks,
SkippedTasks = SkippedTasks,
OverallProgress = OverallProgress,
IsExecuting = IsExecuting
};
}
}
/// <summary>
/// 任务执行统计信息
/// </summary>
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;
}

View File

@@ -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;
/// <summary>
/// 齿轮任务执行器,负责从 JSON 数据解析任务并执行
/// </summary>
public partial class GearTaskExecutor : ObservableObject
{
private readonly ILogger<GearTaskExecutor> _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<GearTaskExecutor> 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;
}
/// <summary>
/// 从 JSON 文件加载并执行任务定义
/// </summary>
/// <param name="taskDefinitionName">任务定义名称</param>
/// <param name="ct">取消令牌</param>
/// <returns></returns>
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;
}
}
/// <summary>
/// 直接执行 GearTaskData
/// </summary>
/// <param name="taskData">任务数据</param>
/// <param name="ct">取消令牌</param>
/// <returns></returns>
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;
}
}
/// <summary>
/// 停止当前执行的任务
/// </summary>
public void StopExecution()
{
if (IsExecuting)
{
StatusMessage = "正在停止任务执行...";
_logger.LogInformation("用户请求停止任务执行");
_executionManager.CancelExecution();
}
}
/// <summary>
/// 处理进度变化事件
/// </summary>
/// <param name="sender">事件发送者</param>
/// <param name="progress">进度值</param>
private void OnProgressChanged(object? sender, double progress)
{
Progress = progress;
}
/// <summary>
/// 处理执行管理器属性变化事件
/// </summary>
/// <param name="sender">事件发送者</param>
/// <param name="e">属性变化事件参数</param>
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;
}
}
/// <summary>
/// 获取执行统计信息
/// </summary>
/// <returns>统计信息</returns>
public TaskExecutionStatistics GetExecutionStatistics()
{
return _executionManager.GetStatistics();
}
/// <summary>
/// 获取当前任务执行信息
/// </summary>
/// <returns>当前任务信息</returns>
public TaskExecutionInfo? GetCurrentTaskInfo()
{
return _executionManager.CurrentTaskInfo;
}
/// <summary>
/// 获取根任务执行信息
/// </summary>
/// <returns></returns>
public TaskExecutionInfo? GetRootTaskInfo()
{
return _executionManager.RootTaskInfo;
}
/// <summary>
/// 将 GearTaskViewModel 转换为 GearTaskData
/// </summary>
/// <param name="viewModel">视图模型</param>
/// <returns>任务数据</returns>
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<GearTaskData>();
foreach (var child in viewModel.Children)
{
taskData.Children.Add(ConvertViewModelToData(child));
}
}
return taskData;
}
}
/// <summary>
/// 空任务实现,用于已禁用的任务
/// </summary>
internal class EmptyGearTask : BaseGearTask
{
public override Task Run(CancellationToken ct)
{
return Task.CompletedTask;
}
}

View File

@@ -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<GearTaskStorageService>();
services.AddSingleton<GearTaskFactory>();
services.AddSingleton<GearTaskConverter>();
services.AddTransient<GearTaskExecutionManager>();
services.AddTransient<GearTaskExecutor>();
```
## 基本使用
### 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<bool> ValidateTaskAsync(GearTaskData taskData)
{
var converter = serviceProvider.GetRequiredService<GearTaskConverter>();
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. **参数验证**:在执行前验证任务数据的完整性
## 注意事项
- 任务执行器是线程安全的,但同时只能执行一个任务
- 子任务会按顺序执行,不支持并行执行
- 禁用的任务会被跳过,但仍会处理其子任务
- 任务失败会停止整个执行流程,除非配置为继续执行
- 所有任务都支持取消操作

View File

@@ -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;
/// <summary>
/// 齿轮任务工厂,根据任务类型和参数创建对应的任务实例
/// </summary>
public class GearTaskFactory
{
private readonly ILogger<GearTaskFactory> _logger;
private readonly IServiceProvider _serviceProvider;
public GearTaskFactory(ILogger<GearTaskFactory> logger, IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
}
/// <summary>
/// 根据 GearTaskData 创建对应的任务实例
/// </summary>
/// <param name="taskData">任务数据</param>
/// <returns>创建的任务实例</returns>
public async Task<BaseGearTask> 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;
}
}
/// <summary>
/// 创建 JavaScript 任务
/// </summary>
private async Task<BaseGearTask> CreateJavascriptTaskAsync(GearTaskData taskData)
{
var parameters = DeserializeParameters<JavascriptGearTaskParams>(taskData.Parameters);
if (string.IsNullOrWhiteSpace(parameters.FolderName))
{
throw new ArgumentException($"JavaScript 任务缺少 FolderName 参数: {taskData.Name}");
}
return new JavascriptGearTask(parameters);
}
/// <summary>
/// 创建路径任务
/// </summary>
private async Task<BaseGearTask> CreatePathingTaskAsync(GearTaskData taskData)
{
var parameters = DeserializeParameters<PathingGearTaskParams>(taskData.Parameters);
if (string.IsNullOrWhiteSpace(parameters.Path))
{
throw new ArgumentException($"Pathing 任务缺少 Path 参数: {taskData.Name}");
}
return new PathingGearTask(parameters);
}
/// <summary>
/// 创建 C# 反射任务
/// </summary>
private async Task<BaseGearTask> CreateCSharpReflectionTaskAsync(GearTaskData taskData)
{
var parameters = DeserializeParameters<CSharpReflectionGearTaskParams>(taskData.Parameters);
if (string.IsNullOrWhiteSpace(parameters.MethodPath))
{
throw new ArgumentException($"C# 反射任务缺少 MethodPath 参数: {taskData.Name}");
}
return new CSharpReflectionGearTask(parameters);
}
/// <summary>
/// 创建键鼠任务
/// </summary>
private async Task<BaseGearTask> 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<dynamic>(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);
}
/// <summary>
/// 创建 Shell 任务
/// </summary>
private async Task<BaseGearTask> CreateShellTaskAsync(GearTaskData taskData)
{
ShellConfig? shellConfig = null;
if (taskData.Parameters != null)
{
try
{
shellConfig = DeserializeParameters<ShellConfig>(taskData.Parameters);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "解析 Shell 配置失败,使用默认配置: {TaskName}", taskData.Name);
shellConfig = new ShellConfig();
}
}
return new ShellGearTask(shellConfig);
}
/// <summary>
/// 反序列化参数对象
/// </summary>
/// <typeparam name="T">参数类型</typeparam>
/// <param name="parameters">参数对象</param>
/// <returns>反序列化后的参数</returns>
private T DeserializeParameters<T>(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<T>(json);
return result ?? new T();
}
catch (Exception ex)
{
_logger.LogWarning(ex, "反序列化参数失败,使用默认参数: {ParameterType}", typeof(T).Name);
return new T();
}
}
/// <summary>
/// 获取支持的任务类型列表
/// </summary>
/// <returns>支持的任务类型</returns>
public static string[] GetSupportedTaskTypes()
{
return new[]
{
"javascript",
"pathing",
"csharp",
"csharpreflection",
"keymouse",
"shell"
};
}
/// <summary>
/// 检查任务类型是否受支持
/// </summary>
/// <param name="taskType">任务类型</param>
/// <returns>是否支持</returns>
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));
}
}

View File

@@ -0,0 +1,78 @@
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace BetterGenshinImpact.Service;
/// <summary>
/// 齿轮任务服务注册扩展方法
/// </summary>
public static class GearTaskServiceExtensions
{
/// <summary>
/// 注册齿轮任务相关服务
/// </summary>
/// <param name="services">服务集合</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddGearTaskServices(this IServiceCollection services)
{
// 注册核心服务
services.AddSingleton<GearTaskStorageService>();
services.AddSingleton<GearTaskFactory>();
services.AddSingleton<GearTaskConverter>();
services.AddTransient<GearTaskExecutionManager>();
services.AddTransient<GearTaskExecutor>();
return services;
}
/// <summary>
/// 注册齿轮任务相关服务(带自定义配置)
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="configureOptions">配置选项</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddGearTaskServices(this IServiceCollection services,
Action<GearTaskServiceOptions>? configureOptions = null)
{
var options = new GearTaskServiceOptions();
configureOptions?.Invoke(options);
// 注册配置
services.AddSingleton(options);
// 注册核心服务
return services.AddGearTaskServices();
}
}
/// <summary>
/// 齿轮任务服务配置选项
/// </summary>
public class GearTaskServiceOptions
{
/// <summary>
/// 是否启用详细日志记录
/// </summary>
public bool EnableVerboseLogging { get; set; } = false;
/// <summary>
/// 任务执行超时时间毫秒0 表示无超时
/// </summary>
public int TaskExecutionTimeoutMs { get; set; } = 0;
/// <summary>
/// 是否在任务失败时继续执行后续任务
/// </summary>
public bool ContinueOnTaskFailure { get; set; } = false;
/// <summary>
/// 最大并发任务数0 表示无限制
/// </summary>
public int MaxConcurrentTasks { get; set; } = 1;
/// <summary>
/// 任务存储路径null 表示使用默认路径
/// </summary>
public string? TaskStoragePath { get; set; }
}