mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
任务管理与执行
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
namespace BetterGenshinImpact.Model.Gear.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// 为了和其他Trigger做区分,使用Gear(齿轮)来作为前缀命名调度器内定义的触发器
|
||||
/// </summary>
|
||||
public class GearTrigger
|
||||
{
|
||||
|
||||
}
|
||||
329
BetterGenshinImpact/Service/GearTask/GearTaskConverter.cs
Normal file
329
BetterGenshinImpact/Service/GearTask/GearTaskConverter.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
487
BetterGenshinImpact/Service/GearTask/GearTaskExecutionManager.cs
Normal file
487
BetterGenshinImpact/Service/GearTask/GearTaskExecutionManager.cs
Normal 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;
|
||||
}
|
||||
289
BetterGenshinImpact/Service/GearTask/GearTaskExecutor.cs
Normal file
289
BetterGenshinImpact/Service/GearTask/GearTaskExecutor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
329
BetterGenshinImpact/Service/GearTask/GearTaskExecutor_Usage.md
Normal file
329
BetterGenshinImpact/Service/GearTask/GearTaskExecutor_Usage.md
Normal 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. **参数验证**:在执行前验证任务数据的完整性
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 任务执行器是线程安全的,但同时只能执行一个任务
|
||||
- 子任务会按顺序执行,不支持并行执行
|
||||
- 禁用的任务会被跳过,但仍会处理其子任务
|
||||
- 任务失败会停止整个执行流程,除非配置为继续执行
|
||||
- 所有任务都支持取消操作
|
||||
251
BetterGenshinImpact/Service/GearTask/GearTaskFactory.cs
Normal file
251
BetterGenshinImpact/Service/GearTask/GearTaskFactory.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
Reference in New Issue
Block a user