mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
Implement Quartz.NET integration with dynamic task management
Co-authored-by: huiyadanli <15783049+huiyadanli@users.noreply.github.com>
This commit is contained in:
@@ -129,6 +129,18 @@ public partial class App : Application
|
||||
services.AddSingleton(sp=> sp.GetRequiredService<HomePageViewModel>().Config.HardwareAccelerationConfig);
|
||||
services.AddSingleton<BgiOnnxFactory>();
|
||||
|
||||
// Quartz.NET 服务
|
||||
services.AddQuartz(q =>
|
||||
{
|
||||
// 使用内存存储
|
||||
q.UseInMemoryStore();
|
||||
// 使用默认线程池
|
||||
q.UseDefaultThreadPool(tp => tp.MaxConcurrency = 10);
|
||||
});
|
||||
services.AddSingleton<Service.Quartz.SchedulerManager>();
|
||||
services.AddSingleton<Service.Quartz.DynamicTaskExampleService>();
|
||||
services.AddHostedService<Service.Quartz.QuartzHostedService>();
|
||||
|
||||
// Configuration
|
||||
//services.Configure<AppConfig>(context.Configuration.GetSection(nameof(AppConfig)));
|
||||
}
|
||||
|
||||
@@ -85,6 +85,8 @@
|
||||
<PackageReference Include="WPF-UI.Violeta" Version="4.0.2.3" />
|
||||
<PackageReference Include="gong-wpf-dragdrop" Version="3.2.1" />
|
||||
<PackageReference Include="YoloSharp" Version="6.0.3" />
|
||||
<PackageReference Include="Quartz" Version="3.13.1" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.13.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug'">
|
||||
|
||||
224
BetterGenshinImpact/Examples/QuartzExampleProgram.cs
Normal file
224
BetterGenshinImpact/Examples/QuartzExampleProgram.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using BetterGenshinImpact.Service.Quartz;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Quartz;
|
||||
|
||||
namespace BetterGenshinImpact.Examples;
|
||||
|
||||
/// <summary>
|
||||
/// Quartz.NET 动态任务管理示例程序
|
||||
/// 演示如何在控制台应用中使用调度功能
|
||||
/// </summary>
|
||||
public class QuartzExampleProgram
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("=== Quartz.NET 动态任务管理示例 ===\n");
|
||||
|
||||
// 创建主机和服务
|
||||
var host = CreateHostBuilder(args).Build();
|
||||
|
||||
try
|
||||
{
|
||||
// 启动主机
|
||||
await host.StartAsync();
|
||||
|
||||
// 获取服务
|
||||
var schedulerManager = host.Services.GetRequiredService<SchedulerManager>();
|
||||
var dynamicTaskService = host.Services.GetRequiredService<DynamicTaskExampleService>();
|
||||
|
||||
// 运行示例
|
||||
await RunExamples(schedulerManager, dynamicTaskService);
|
||||
|
||||
Console.WriteLine("\n按任意键退出...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
finally
|
||||
{
|
||||
await host.StopAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureServices((context, services) =>
|
||||
{
|
||||
// 注册 Quartz.NET
|
||||
services.AddQuartz(q =>
|
||||
{
|
||||
q.UseInMemoryStore();
|
||||
q.UseDefaultThreadPool(tp => tp.MaxConcurrency = 5);
|
||||
});
|
||||
|
||||
// 注册自定义服务
|
||||
services.AddSingleton<SchedulerManager>();
|
||||
services.AddSingleton<DynamicTaskExampleService>();
|
||||
services.AddHostedService<QuartzHostedService>();
|
||||
|
||||
// 注册模拟的脚本服务
|
||||
services.AddSingleton<MockScriptService>();
|
||||
});
|
||||
|
||||
private static async Task RunExamples(SchedulerManager schedulerManager, DynamicTaskExampleService dynamicTaskService)
|
||||
{
|
||||
// 创建示例脚本组
|
||||
var scriptGroup1 = CreateExampleScriptGroup("每日任务组", "Daily");
|
||||
var scriptGroup2 = CreateExampleScriptGroup("每周任务组", "Monday");
|
||||
var scriptGroup3 = CreateExampleScriptGroup("自定义任务组", "0 0/15 * * * ? *"); // 每15分钟
|
||||
|
||||
Console.WriteLine("1. 添加定时任务示例");
|
||||
Console.WriteLine("===================");
|
||||
|
||||
// 示例1:添加每日任务
|
||||
Console.WriteLine("添加每日任务(每天8:30执行)...");
|
||||
bool success1 = await dynamicTaskService.CreateDailyTaskAsync(scriptGroup1, 8, 30);
|
||||
Console.WriteLine($"结果: {(success1 ? "成功" : "失败")}\n");
|
||||
|
||||
// 示例2:添加每周任务
|
||||
Console.WriteLine("添加每周任务(每周一9:00执行)...");
|
||||
bool success2 = await dynamicTaskService.CreateWeeklyTaskAsync(scriptGroup2, 1, 9, 0);
|
||||
Console.WriteLine($"结果: {(success2 ? "成功" : "失败")}\n");
|
||||
|
||||
// 示例3:添加自定义Cron表达式任务
|
||||
Console.WriteLine("添加自定义任务(每15分钟执行)...");
|
||||
bool success3 = await schedulerManager.AddScheduledTaskAsync(scriptGroup3, "0 0/15 * * * ? *");
|
||||
Console.WriteLine($"结果: {(success3 ? "成功" : "失败")}\n");
|
||||
|
||||
// 示例4:批量添加任务
|
||||
Console.WriteLine("2. 批量操作示例");
|
||||
Console.WriteLine("================");
|
||||
var scriptGroups = new List<ScriptGroup> { scriptGroup1, scriptGroup2, scriptGroup3 };
|
||||
int batchCount = await dynamicTaskService.AddMultipleScriptGroupSchedulesAsync(scriptGroups, "0 0 12 * * ? *");
|
||||
Console.WriteLine($"批量添加任务结果: 成功添加 {batchCount} 个任务\n");
|
||||
|
||||
// 示例5:查看所有任务
|
||||
Console.WriteLine("3. 查看任务状态");
|
||||
Console.WriteLine("===============");
|
||||
var allTasks = await schedulerManager.GetAllScheduledTasksAsync();
|
||||
Console.WriteLine($"当前总任务数: {allTasks.Count}");
|
||||
foreach (var task in allTasks)
|
||||
{
|
||||
Console.WriteLine($"- 任务: {task.JobName}");
|
||||
Console.WriteLine($" 脚本组: {task.ScriptGroupName}");
|
||||
Console.WriteLine($" Cron表达式: {task.CronExpression}");
|
||||
Console.WriteLine($" 下次执行: {task.NextFireTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "未安排"}");
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
// 示例6:生成任务报告
|
||||
Console.WriteLine("4. 任务执行报告");
|
||||
Console.WriteLine("===============");
|
||||
var report = await dynamicTaskService.GetScheduledTaskReportAsync();
|
||||
Console.WriteLine($"总任务数: {report.TotalTasks}");
|
||||
Console.WriteLine($"活跃任务数: {report.ActiveTasks}");
|
||||
Console.WriteLine("按脚本组统计:");
|
||||
foreach (var group in report.TasksByScriptGroup)
|
||||
{
|
||||
Console.WriteLine($" {group.Key}: {group.Value} 个任务");
|
||||
}
|
||||
Console.WriteLine("\n即将执行的任务:");
|
||||
foreach (var execution in report.NextExecutions.Take(5))
|
||||
{
|
||||
Console.WriteLine($" {execution.ScriptGroupName} - {execution.NextFireTime:yyyy-MM-dd HH:mm:ss}");
|
||||
}
|
||||
Console.WriteLine();
|
||||
|
||||
// 示例7:任务管理操作
|
||||
Console.WriteLine("5. 任务管理操作");
|
||||
Console.WriteLine("===============");
|
||||
|
||||
// 暂停任务
|
||||
if (allTasks.Count > 0)
|
||||
{
|
||||
var firstTask = allTasks[0];
|
||||
Console.WriteLine($"暂停任务: {firstTask.JobName}");
|
||||
bool pauseResult = await schedulerManager.PauseScheduledTaskAsync(firstTask.JobName);
|
||||
Console.WriteLine($"暂停结果: {(pauseResult ? "成功" : "失败")}");
|
||||
|
||||
// 等待一秒
|
||||
await Task.Delay(1000);
|
||||
|
||||
// 恢复任务
|
||||
Console.WriteLine($"恢复任务: {firstTask.JobName}");
|
||||
bool resumeResult = await schedulerManager.ResumeScheduledTaskAsync(firstTask.JobName);
|
||||
Console.WriteLine($"恢复结果: {(resumeResult ? "成功" : "失败")}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// 示例8:更新任务
|
||||
if (allTasks.Count > 0)
|
||||
{
|
||||
var firstTask = allTasks[0];
|
||||
var newCron = "0 0 10 * * ? *"; // 每天10点执行
|
||||
Console.WriteLine($"更新任务Cron表达式: {firstTask.JobName}");
|
||||
Console.WriteLine($"新的Cron表达式: {newCron}");
|
||||
bool updateResult = await schedulerManager.UpdateScheduledTaskAsync(firstTask.JobName, newCron);
|
||||
Console.WriteLine($"更新结果: {(updateResult ? "成功" : "失败")}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
// 示例9:清理任务
|
||||
Console.WriteLine("6. 清理任务");
|
||||
Console.WriteLine("===========");
|
||||
int cleanupCount = await dynamicTaskService.ClearAllScheduledTasksAsync();
|
||||
Console.WriteLine($"清理结果: 删除了 {cleanupCount} 个任务");
|
||||
}
|
||||
|
||||
private static ScriptGroup CreateExampleScriptGroup(string name, string schedule)
|
||||
{
|
||||
var scriptGroup = new ScriptGroup
|
||||
{
|
||||
Name = name,
|
||||
Index = 1
|
||||
};
|
||||
|
||||
// 添加示例项目
|
||||
var project = new ScriptGroupProject
|
||||
{
|
||||
Name = $"{name}_脚本",
|
||||
FolderName = "example",
|
||||
Type = "Javascript",
|
||||
Status = "Enabled",
|
||||
Schedule = schedule,
|
||||
Index = 1
|
||||
};
|
||||
|
||||
scriptGroup.AddProject(project);
|
||||
return scriptGroup;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟脚本服务,用于演示
|
||||
/// </summary>
|
||||
public class MockScriptService : BetterGenshinImpact.Service.Interface.IScriptService
|
||||
{
|
||||
private readonly ILogger<MockScriptService> _logger;
|
||||
|
||||
public MockScriptService(ILogger<MockScriptService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task RunMulti(List<ScriptGroupProject> projects, string groupName, object? taskProgress)
|
||||
{
|
||||
_logger.LogInformation("模拟执行脚本组: {GroupName},包含 {ProjectCount} 个项目", groupName, projects.Count);
|
||||
|
||||
foreach (var project in projects)
|
||||
{
|
||||
_logger.LogInformation("执行项目: {ProjectName} ({ProjectType})", project.Name, project.Type);
|
||||
// 模拟执行时间
|
||||
await Task.Delay(100);
|
||||
}
|
||||
|
||||
_logger.LogInformation("脚本组 {GroupName} 执行完成", groupName);
|
||||
}
|
||||
}
|
||||
389
BetterGenshinImpact/Service/Quartz/CronExpressionHelper.cs
Normal file
389
BetterGenshinImpact/Service/Quartz/CronExpressionHelper.cs
Normal file
@@ -0,0 +1,389 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BetterGenshinImpact.Service.Quartz;
|
||||
|
||||
/// <summary>
|
||||
/// Cron 表达式帮助类
|
||||
/// 提供常用的 Cron 表达式生成和解析功能
|
||||
/// </summary>
|
||||
public static class CronExpressionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 预定义的 Cron 表达式集合
|
||||
/// </summary>
|
||||
public static readonly Dictionary<string, string> PredefinedExpressions = new()
|
||||
{
|
||||
// 基本时间间隔
|
||||
{ "每分钟", "0 * * * * ? *" },
|
||||
{ "每5分钟", "0 0/5 * * * ? *" },
|
||||
{ "每10分钟", "0 0/10 * * * ? *" },
|
||||
{ "每15分钟", "0 0/15 * * * ? *" },
|
||||
{ "每30分钟", "0 0/30 * * * ? *" },
|
||||
{ "每小时", "0 0 * * * ? *" },
|
||||
{ "每2小时", "0 0 0/2 * * ? *" },
|
||||
{ "每4小时", "0 0 0/4 * * ? *" },
|
||||
{ "每6小时", "0 0 0/6 * * ? *" },
|
||||
{ "每12小时", "0 0 0/12 * * ? *" },
|
||||
|
||||
// 每日时间点
|
||||
{ "每天午夜", "0 0 0 * * ? *" },
|
||||
{ "每天早上6点", "0 0 6 * * ? *" },
|
||||
{ "每天早上8点", "0 0 8 * * ? *" },
|
||||
{ "每天上午9点", "0 0 9 * * ? *" },
|
||||
{ "每天中午12点", "0 0 12 * * ? *" },
|
||||
{ "每天下午6点", "0 0 18 * * ? *" },
|
||||
{ "每天晚上9点", "0 0 21 * * ? *" },
|
||||
{ "每天晚上11点", "0 0 23 * * ? *" },
|
||||
|
||||
// 工作日和周末
|
||||
{ "工作日上午9点", "0 0 9 ? * MON-FRI *" },
|
||||
{ "工作日下午6点", "0 0 18 ? * MON-FRI *" },
|
||||
{ "周末上午10点", "0 0 10 ? * SAT,SUN *" },
|
||||
|
||||
// 每周特定时间
|
||||
{ "每周一上午9点", "0 0 9 ? * MON *" },
|
||||
{ "每周二上午9点", "0 0 9 ? * TUE *" },
|
||||
{ "每周三上午9点", "0 0 9 ? * WED *" },
|
||||
{ "每周四上午9点", "0 0 9 ? * THU *" },
|
||||
{ "每周五上午9点", "0 0 9 ? * FRI *" },
|
||||
{ "每周六上午10点", "0 0 10 ? * SAT *" },
|
||||
{ "每周日上午10点", "0 0 10 ? * SUN *" },
|
||||
|
||||
// 每月特定时间
|
||||
{ "每月1号午夜", "0 0 0 1 * ? *" },
|
||||
{ "每月15号午夜", "0 0 0 15 * ? *" },
|
||||
{ "每月最后一天", "0 0 0 L * ? *" },
|
||||
{ "每月第一个周一", "0 0 0 ? * MON#1 *" },
|
||||
{ "每月最后一个周五", "0 0 0 ? * FRIL *" },
|
||||
|
||||
// 季度和年度
|
||||
{ "每季度第一天", "0 0 0 1 1/3 ? *" },
|
||||
{ "每年1月1日", "0 0 0 1 1 ? *" },
|
||||
{ "每年生日提醒", "0 0 9 1 1 ? *" } // 示例:每年1月1日上午9点
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 创建每日执行的 Cron 表达式
|
||||
/// </summary>
|
||||
/// <param name="hour">小时 (0-23)</param>
|
||||
/// <param name="minute">分钟 (0-59)</param>
|
||||
/// <param name="second">秒 (0-59,默认0)</param>
|
||||
/// <returns>Cron 表达式</returns>
|
||||
public static string CreateDaily(int hour, int minute, int second = 0)
|
||||
{
|
||||
ValidateTime(hour, minute, second);
|
||||
return $"{second} {minute} {hour} * * ? *";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建每周执行的 Cron 表达式
|
||||
/// </summary>
|
||||
/// <param name="dayOfWeek">星期几 (1=周一, 7=周日)</param>
|
||||
/// <param name="hour">小时 (0-23)</param>
|
||||
/// <param name="minute">分钟 (0-59)</param>
|
||||
/// <param name="second">秒 (0-59,默认0)</param>
|
||||
/// <returns>Cron 表达式</returns>
|
||||
public static string CreateWeekly(DayOfWeek dayOfWeek, int hour, int minute, int second = 0)
|
||||
{
|
||||
ValidateTime(hour, minute, second);
|
||||
var dayNames = new[] { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" };
|
||||
var dayName = dayNames[(int)dayOfWeek];
|
||||
return $"{second} {minute} {hour} ? * {dayName} *";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建每月执行的 Cron 表达式
|
||||
/// </summary>
|
||||
/// <param name="dayOfMonth">月中的第几天 (1-31)</param>
|
||||
/// <param name="hour">小时 (0-23)</param>
|
||||
/// <param name="minute">分钟 (0-59)</param>
|
||||
/// <param name="second">秒 (0-59,默认0)</param>
|
||||
/// <returns>Cron 表达式</returns>
|
||||
public static string CreateMonthly(int dayOfMonth, int hour, int minute, int second = 0)
|
||||
{
|
||||
if (dayOfMonth < 1 || dayOfMonth > 31)
|
||||
throw new ArgumentException("月中的天数必须在1-31之间", nameof(dayOfMonth));
|
||||
|
||||
ValidateTime(hour, minute, second);
|
||||
return $"{second} {minute} {hour} {dayOfMonth} * ? *";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建间隔执行的 Cron 表达式
|
||||
/// </summary>
|
||||
/// <param name="intervalMinutes">间隔分钟数</param>
|
||||
/// <param name="startMinute">开始分钟 (默认0)</param>
|
||||
/// <returns>Cron 表达式</returns>
|
||||
public static string CreateInterval(int intervalMinutes, int startMinute = 0)
|
||||
{
|
||||
if (intervalMinutes <= 0 || intervalMinutes > 59)
|
||||
throw new ArgumentException("间隔分钟数必须在1-59之间", nameof(intervalMinutes));
|
||||
|
||||
if (startMinute < 0 || startMinute > 59)
|
||||
throw new ArgumentException("开始分钟必须在0-59之间", nameof(startMinute));
|
||||
|
||||
return $"0 {startMinute}/{intervalMinutes} * * * ? *";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建工作日执行的 Cron 表达式
|
||||
/// </summary>
|
||||
/// <param name="hour">小时 (0-23)</param>
|
||||
/// <param name="minute">分钟 (0-59)</param>
|
||||
/// <param name="second">秒 (0-59,默认0)</param>
|
||||
/// <returns>Cron 表达式</returns>
|
||||
public static string CreateWorkdays(int hour, int minute, int second = 0)
|
||||
{
|
||||
ValidateTime(hour, minute, second);
|
||||
return $"{second} {minute} {hour} ? * MON-FRI *";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建周末执行的 Cron 表达式
|
||||
/// </summary>
|
||||
/// <param name="hour">小时 (0-23)</param>
|
||||
/// <param name="minute">分钟 (0-59)</param>
|
||||
/// <param name="second">秒 (0-59,默认0)</param>
|
||||
/// <returns>Cron 表达式</returns>
|
||||
public static string CreateWeekends(int hour, int minute, int second = 0)
|
||||
{
|
||||
ValidateTime(hour, minute, second);
|
||||
return $"{second} {minute} {hour} ? * SAT,SUN *";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建多个时间点执行的 Cron 表达式
|
||||
/// </summary>
|
||||
/// <param name="hours">小时数组</param>
|
||||
/// <param name="minute">分钟</param>
|
||||
/// <param name="second">秒 (默认0)</param>
|
||||
/// <returns>Cron 表达式</returns>
|
||||
public static string CreateMultipleHours(int[] hours, int minute, int second = 0)
|
||||
{
|
||||
if (hours == null || hours.Length == 0)
|
||||
throw new ArgumentException("小时数组不能为空", nameof(hours));
|
||||
|
||||
foreach (var hour in hours)
|
||||
{
|
||||
if (hour < 0 || hour > 23)
|
||||
throw new ArgumentException($"小时 {hour} 必须在0-23之间", nameof(hours));
|
||||
}
|
||||
|
||||
if (minute < 0 || minute > 59)
|
||||
throw new ArgumentException("分钟必须在0-59之间", nameof(minute));
|
||||
|
||||
if (second < 0 || second > 59)
|
||||
throw new ArgumentException("秒必须在0-59之间", nameof(second));
|
||||
|
||||
var hoursStr = string.Join(",", hours);
|
||||
return $"{second} {minute} {hoursStr} * * ? *";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 Cron 表达式为人类可读的描述
|
||||
/// </summary>
|
||||
/// <param name="cronExpression">Cron 表达式</param>
|
||||
/// <returns>描述文本</returns>
|
||||
public static string ParseToDescription(string cronExpression)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(cronExpression))
|
||||
return "无效的Cron表达式";
|
||||
|
||||
// 查找预定义表达式
|
||||
foreach (var predefined in PredefinedExpressions)
|
||||
{
|
||||
if (predefined.Value.Equals(cronExpression, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return predefined.Key;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var parts = cronExpression.Trim().Split(' ');
|
||||
if (parts.Length < 6)
|
||||
return "格式不正确的Cron表达式";
|
||||
|
||||
var second = parts[0];
|
||||
var minute = parts[1];
|
||||
var hour = parts[2];
|
||||
var day = parts[3];
|
||||
var month = parts[4];
|
||||
var dayOfWeek = parts[5];
|
||||
|
||||
var description = "自定义时间: ";
|
||||
|
||||
// 解析秒
|
||||
if (second != "*" && second != "0")
|
||||
{
|
||||
description += $"第{second}秒 ";
|
||||
}
|
||||
|
||||
// 解析分钟
|
||||
if (minute.Contains("/"))
|
||||
{
|
||||
var intervalParts = minute.Split('/');
|
||||
if (intervalParts.Length == 2 && intervalParts[0] == "0")
|
||||
{
|
||||
description += $"每{intervalParts[1]}分钟 ";
|
||||
}
|
||||
}
|
||||
else if (minute != "*")
|
||||
{
|
||||
description += $"{minute}分 ";
|
||||
}
|
||||
|
||||
// 解析小时
|
||||
if (hour.Contains("/"))
|
||||
{
|
||||
var intervalParts = hour.Split('/');
|
||||
if (intervalParts.Length == 2 && intervalParts[0] == "0")
|
||||
{
|
||||
description += $"每{intervalParts[1]}小时 ";
|
||||
}
|
||||
}
|
||||
else if (hour.Contains(","))
|
||||
{
|
||||
description += $"在{hour.Replace(",", "、")}点 ";
|
||||
}
|
||||
else if (hour != "*")
|
||||
{
|
||||
description += $"{hour}点 ";
|
||||
}
|
||||
|
||||
// 解析星期
|
||||
if (dayOfWeek != "*" && dayOfWeek != "?")
|
||||
{
|
||||
var dayNames = new Dictionary<string, string>
|
||||
{
|
||||
{ "MON", "周一" }, { "TUE", "周二" }, { "WED", "周三" }, { "THU", "周四" },
|
||||
{ "FRI", "周五" }, { "SAT", "周六" }, { "SUN", "周日" },
|
||||
{ "MON-FRI", "工作日" }, { "SAT,SUN", "周末" }
|
||||
};
|
||||
|
||||
if (dayNames.TryGetValue(dayOfWeek, out var dayName))
|
||||
{
|
||||
description += $"({dayName}) ";
|
||||
}
|
||||
else
|
||||
{
|
||||
description += $"({dayOfWeek}) ";
|
||||
}
|
||||
}
|
||||
|
||||
// 解析月中的天
|
||||
if (day != "*" && day != "?")
|
||||
{
|
||||
if (day == "L")
|
||||
{
|
||||
description += "(月末) ";
|
||||
}
|
||||
else
|
||||
{
|
||||
description += $"(每月{day}号) ";
|
||||
}
|
||||
}
|
||||
|
||||
return description.Trim();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "无法解析的Cron表达式";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 Cron 表达式是否有效
|
||||
/// </summary>
|
||||
/// <param name="cronExpression">Cron 表达式</param>
|
||||
/// <returns>是否有效</returns>
|
||||
public static bool IsValidCronExpression(string cronExpression)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(cronExpression))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// 使用 Quartz.NET 的 CronExpression 进行验证
|
||||
var expression = new Quartz.CronExpression(cronExpression);
|
||||
return expression.IsSatisfiedBy(DateTime.Now);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Cron 表达式的下次执行时间
|
||||
/// </summary>
|
||||
/// <param name="cronExpression">Cron 表达式</param>
|
||||
/// <param name="fromTime">起始时间(可选,默认为当前时间)</param>
|
||||
/// <returns>下次执行时间,如果表达式无效则返回null</returns>
|
||||
public static DateTime? GetNextExecutionTime(string cronExpression, DateTime? fromTime = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var expression = new Quartz.CronExpression(cronExpression);
|
||||
var baseTime = fromTime ?? DateTime.Now;
|
||||
var nextTime = expression.GetNextValidTimeAfter(baseTime);
|
||||
return nextTime?.DateTime;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 Cron 表达式的多个执行时间
|
||||
/// </summary>
|
||||
/// <param name="cronExpression">Cron 表达式</param>
|
||||
/// <param name="count">获取的执行时间数量</param>
|
||||
/// <param name="fromTime">起始时间(可选,默认为当前时间)</param>
|
||||
/// <returns>执行时间列表</returns>
|
||||
public static List<DateTime> GetNextExecutionTimes(string cronExpression, int count, DateTime? fromTime = null)
|
||||
{
|
||||
var times = new List<DateTime>();
|
||||
|
||||
try
|
||||
{
|
||||
var expression = new Quartz.CronExpression(cronExpression);
|
||||
var currentTime = fromTime ?? DateTime.Now;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var nextTime = expression.GetNextValidTimeAfter(currentTime);
|
||||
if (nextTime.HasValue)
|
||||
{
|
||||
times.Add(nextTime.Value.DateTime);
|
||||
currentTime = nextTime.Value.DateTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 表达式无效,返回空列表
|
||||
}
|
||||
|
||||
return times;
|
||||
}
|
||||
|
||||
private static void ValidateTime(int hour, int minute, int second)
|
||||
{
|
||||
if (hour < 0 || hour > 23)
|
||||
throw new ArgumentException("小时必须在0-23之间", nameof(hour));
|
||||
|
||||
if (minute < 0 || minute > 59)
|
||||
throw new ArgumentException("分钟必须在0-59之间", nameof(minute));
|
||||
|
||||
if (second < 0 || second > 59)
|
||||
throw new ArgumentException("秒必须在0-59之间", nameof(second));
|
||||
}
|
||||
}
|
||||
269
BetterGenshinImpact/Service/Quartz/DynamicTaskExampleService.cs
Normal file
269
BetterGenshinImpact/Service/Quartz/DynamicTaskExampleService.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace BetterGenshinImpact.Service.Quartz;
|
||||
|
||||
/// <summary>
|
||||
/// 动态任务管理示例服务
|
||||
/// 提供动态添加、删除、管理定时任务的示例方法
|
||||
/// </summary>
|
||||
public class DynamicTaskExampleService
|
||||
{
|
||||
private readonly SchedulerManager _schedulerManager;
|
||||
private readonly ILogger<DynamicTaskExampleService> _logger;
|
||||
|
||||
public DynamicTaskExampleService(SchedulerManager schedulerManager, ILogger<DynamicTaskExampleService> logger)
|
||||
{
|
||||
_schedulerManager = schedulerManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:为脚本组添加定时任务
|
||||
/// </summary>
|
||||
/// <param name="scriptGroup">脚本组</param>
|
||||
/// <param name="cronExpression">Cron表达式</param>
|
||||
/// <returns>任务是否添加成功</returns>
|
||||
public async Task<bool> AddScriptGroupScheduleAsync(ScriptGroup scriptGroup, string cronExpression)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查脚本组是否有效
|
||||
if (scriptGroup?.Projects == null || !scriptGroup.Projects.Any())
|
||||
{
|
||||
_logger.LogWarning("脚本组 {ScriptGroupName} 没有有效的项目", scriptGroup?.Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否有启用的项目
|
||||
var enabledProjects = scriptGroup.Projects.Where(p => p.Status == "Enabled").ToList();
|
||||
if (enabledProjects.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("脚本组 {ScriptGroupName} 没有启用的项目", scriptGroup.Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加定时任务
|
||||
return await _schedulerManager.AddScheduledTaskAsync(scriptGroup, cronExpression);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "添加脚本组定时任务失败:{Message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:批量添加多个脚本组的定时任务
|
||||
/// </summary>
|
||||
/// <param name="scriptGroups">脚本组列表</param>
|
||||
/// <param name="defaultCronExpression">默认Cron表达式</param>
|
||||
/// <returns>成功添加的任务数量</returns>
|
||||
public async Task<int> AddMultipleScriptGroupSchedulesAsync(List<ScriptGroup> scriptGroups, string defaultCronExpression = "0 0 0 * * ? *")
|
||||
{
|
||||
int successCount = 0;
|
||||
|
||||
foreach (var scriptGroup in scriptGroups)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用脚本组的调度配置或默认配置
|
||||
var cronExpression = !string.IsNullOrEmpty(scriptGroup.Projects?.FirstOrDefault()?.Schedule)
|
||||
? SchedulerManager.ConvertScheduleToCron(scriptGroup.Projects.FirstOrDefault()!.Schedule)
|
||||
: defaultCronExpression;
|
||||
|
||||
if (await AddScriptGroupScheduleAsync(scriptGroup, cronExpression))
|
||||
{
|
||||
successCount++;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "添加脚本组 {ScriptGroupName} 定时任务失败:{Message}", scriptGroup.Name, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("批量添加定时任务完成,成功添加 {SuccessCount}/{TotalCount} 个任务", successCount, scriptGroups.Count);
|
||||
return successCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:根据脚本组名称删除定时任务
|
||||
/// </summary>
|
||||
/// <param name="scriptGroupName">脚本组名称</param>
|
||||
/// <returns>删除的任务数量</returns>
|
||||
public async Task<int> RemoveScriptGroupSchedulesAsync(string scriptGroupName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var allTasks = await _schedulerManager.GetAllScheduledTasksAsync();
|
||||
var tasksToRemove = allTasks.Where(t => t.ScriptGroupName == scriptGroupName).ToList();
|
||||
|
||||
int removedCount = 0;
|
||||
foreach (var task in tasksToRemove)
|
||||
{
|
||||
if (await _schedulerManager.RemoveScheduledTaskAsync(task.JobName))
|
||||
{
|
||||
removedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("删除脚本组 {ScriptGroupName} 的定时任务完成,成功删除 {RemovedCount} 个任务", scriptGroupName, removedCount);
|
||||
return removedCount;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "删除脚本组 {ScriptGroupName} 定时任务失败:{Message}", scriptGroupName, ex.Message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:创建每日执行的定时任务
|
||||
/// </summary>
|
||||
/// <param name="scriptGroup">脚本组</param>
|
||||
/// <param name="hour">执行小时(0-23)</param>
|
||||
/// <param name="minute">执行分钟(0-59)</param>
|
||||
/// <returns>任务是否创建成功</returns>
|
||||
public async Task<bool> CreateDailyTaskAsync(ScriptGroup scriptGroup, int hour = 0, int minute = 0)
|
||||
{
|
||||
if (hour < 0 || hour > 23 || minute < 0 || minute > 59)
|
||||
{
|
||||
_logger.LogError("时间参数无效:小时 {Hour},分钟 {Minute}", hour, minute);
|
||||
return false;
|
||||
}
|
||||
|
||||
var cronExpression = $"0 {minute} {hour} * * ? *";
|
||||
return await AddScriptGroupScheduleAsync(scriptGroup, cronExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:创建每周执行的定时任务
|
||||
/// </summary>
|
||||
/// <param name="scriptGroup">脚本组</param>
|
||||
/// <param name="dayOfWeek">星期几(1=周一,7=周日)</param>
|
||||
/// <param name="hour">执行小时(0-23)</param>
|
||||
/// <param name="minute">执行分钟(0-59)</param>
|
||||
/// <returns>任务是否创建成功</returns>
|
||||
public async Task<bool> CreateWeeklyTaskAsync(ScriptGroup scriptGroup, int dayOfWeek, int hour = 0, int minute = 0)
|
||||
{
|
||||
if (dayOfWeek < 1 || dayOfWeek > 7 || hour < 0 || hour > 23 || minute < 0 || minute > 59)
|
||||
{
|
||||
_logger.LogError("参数无效:星期 {DayOfWeek},小时 {Hour},分钟 {Minute}", dayOfWeek, hour, minute);
|
||||
return false;
|
||||
}
|
||||
|
||||
var dayNames = new[] { "", "MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN" };
|
||||
var cronExpression = $"0 {minute} {hour} ? * {dayNames[dayOfWeek]} *";
|
||||
return await AddScriptGroupScheduleAsync(scriptGroup, cronExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:创建间隔执行的定时任务
|
||||
/// </summary>
|
||||
/// <param name="scriptGroup">脚本组</param>
|
||||
/// <param name="intervalMinutes">间隔分钟数</param>
|
||||
/// <returns>任务是否创建成功</returns>
|
||||
public async Task<bool> CreateIntervalTaskAsync(ScriptGroup scriptGroup, int intervalMinutes)
|
||||
{
|
||||
if (intervalMinutes <= 0)
|
||||
{
|
||||
_logger.LogError("间隔时间无效:{IntervalMinutes} 分钟", intervalMinutes);
|
||||
return false;
|
||||
}
|
||||
|
||||
var cronExpression = $"0 0/{intervalMinutes} * * * ? *";
|
||||
return await AddScriptGroupScheduleAsync(scriptGroup, cronExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:获取定时任务报告
|
||||
/// </summary>
|
||||
/// <returns>定时任务报告</returns>
|
||||
public async Task<ScheduledTaskReport> GetScheduledTaskReportAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var allTasks = await _schedulerManager.GetAllScheduledTasksAsync();
|
||||
|
||||
return new ScheduledTaskReport
|
||||
{
|
||||
TotalTasks = allTasks.Count,
|
||||
ActiveTasks = allTasks.Count(t => t.NextFireTime.HasValue),
|
||||
TasksByScriptGroup = allTasks.GroupBy(t => t.ScriptGroupName)
|
||||
.ToDictionary(g => g.Key, g => g.Count()),
|
||||
NextExecutions = allTasks.Where(t => t.NextFireTime.HasValue)
|
||||
.OrderBy(t => t.NextFireTime)
|
||||
.Take(10)
|
||||
.Select(t => new NextExecution
|
||||
{
|
||||
JobName = t.JobName,
|
||||
ScriptGroupName = t.ScriptGroupName,
|
||||
NextFireTime = t.NextFireTime!.Value,
|
||||
CronExpression = t.CronExpression
|
||||
})
|
||||
.ToList()
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取定时任务报告失败:{Message}", ex.Message);
|
||||
return new ScheduledTaskReport();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 示例:清理所有定时任务
|
||||
/// </summary>
|
||||
/// <returns>清理的任务数量</returns>
|
||||
public async Task<int> ClearAllScheduledTasksAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var allTasks = await _schedulerManager.GetAllScheduledTasksAsync();
|
||||
int clearedCount = 0;
|
||||
|
||||
foreach (var task in allTasks)
|
||||
{
|
||||
if (await _schedulerManager.RemoveScheduledTaskAsync(task.JobName))
|
||||
{
|
||||
clearedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("清理定时任务完成,成功清理 {ClearedCount} 个任务", clearedCount);
|
||||
return clearedCount;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "清理定时任务失败:{Message}", ex.Message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时任务报告
|
||||
/// </summary>
|
||||
public class ScheduledTaskReport
|
||||
{
|
||||
public int TotalTasks { get; set; }
|
||||
public int ActiveTasks { get; set; }
|
||||
public Dictionary<string, int> TasksByScriptGroup { get; set; } = new();
|
||||
public List<NextExecution> NextExecutions { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下次执行信息
|
||||
/// </summary>
|
||||
public class NextExecution
|
||||
{
|
||||
public string JobName { get; set; } = "";
|
||||
public string ScriptGroupName { get; set; } = "";
|
||||
public DateTime NextFireTime { get; set; }
|
||||
public string CronExpression { get; set; } = "";
|
||||
}
|
||||
52
BetterGenshinImpact/Service/Quartz/QuartzHostedService.cs
Normal file
52
BetterGenshinImpact/Service/Quartz/QuartzHostedService.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Quartz;
|
||||
|
||||
namespace BetterGenshinImpact.Service.Quartz;
|
||||
|
||||
/// <summary>
|
||||
/// Quartz.NET 调度服务 - 管理定时任务的生命周期
|
||||
/// </summary>
|
||||
public class QuartzHostedService : IHostedService
|
||||
{
|
||||
private readonly IScheduler _scheduler;
|
||||
private readonly ILogger<QuartzHostedService> _logger;
|
||||
|
||||
public QuartzHostedService(IScheduler scheduler, ILogger<QuartzHostedService> logger)
|
||||
{
|
||||
_scheduler = scheduler;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("启动 Quartz.NET 调度服务");
|
||||
await _scheduler.Start(cancellationToken);
|
||||
_logger.LogInformation("Quartz.NET 调度服务启动成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "启动 Quartz.NET 调度服务失败:{Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("停止 Quartz.NET 调度服务");
|
||||
await _scheduler.Shutdown(cancellationToken);
|
||||
_logger.LogInformation("Quartz.NET 调度服务停止成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "停止 Quartz.NET 调度服务失败:{Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
214
BetterGenshinImpact/Service/Quartz/README.md
Normal file
214
BetterGenshinImpact/Service/Quartz/README.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# Quartz.NET 集成使用指南
|
||||
|
||||
## 概述
|
||||
|
||||
本项目已集成 Quartz.NET 调度框架,支持动态添加、删除和管理定时任务。现有的手动执行功能保持不变,新增的调度功能为脚本组提供自动化执行能力。
|
||||
|
||||
## 主要特性
|
||||
|
||||
1. **非破坏性集成**:现有功能完全保留
|
||||
2. **动态任务管理**:运行时添加/删除定时任务
|
||||
3. **Cron表达式支持**:完整的时间调度配置
|
||||
4. **可视化管理**:通过UI界面管理定时任务
|
||||
5. **任务监控**:查看任务状态和执行报告
|
||||
|
||||
## 核心组件
|
||||
|
||||
### 1. ScriptExecutionJob
|
||||
负责实际执行脚本组的任务类,实现了 `IJob` 接口。
|
||||
|
||||
### 2. SchedulerManager
|
||||
提供完整的调度管理功能:
|
||||
- 动态添加任务
|
||||
- 删除任务
|
||||
- 更新任务
|
||||
- 暂停/恢复任务
|
||||
- 查询任务状态
|
||||
|
||||
### 3. DynamicTaskExampleService
|
||||
演示如何使用调度功能的示例服务,包含各种实用方法。
|
||||
|
||||
### 4. QuartzHostedService
|
||||
管理 Quartz.NET 调度器的生命周期。
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本用法
|
||||
|
||||
```csharp
|
||||
// 获取服务实例
|
||||
var schedulerManager = App.GetService<SchedulerManager>();
|
||||
var dynamicTaskService = App.GetService<DynamicTaskExampleService>();
|
||||
|
||||
// 添加每日执行的定时任务
|
||||
var scriptGroup = GetYourScriptGroup();
|
||||
bool success = await dynamicTaskService.CreateDailyTaskAsync(scriptGroup, 8, 30); // 每天8:30执行
|
||||
|
||||
// 添加每周执行的定时任务
|
||||
bool success = await dynamicTaskService.CreateWeeklyTaskAsync(scriptGroup, 1, 9, 0); // 每周一9:00执行
|
||||
|
||||
// 添加间隔执行的定时任务
|
||||
bool success = await dynamicTaskService.CreateIntervalTaskAsync(scriptGroup, 30); // 每30分钟执行
|
||||
```
|
||||
|
||||
### 自定义 Cron 表达式
|
||||
|
||||
```csharp
|
||||
// 直接使用 Cron 表达式
|
||||
var cronExpression = "0 0 8,12,18 * * ? *"; // 每天8点、12点、18点执行
|
||||
bool success = await schedulerManager.AddScheduledTaskAsync(scriptGroup, cronExpression);
|
||||
|
||||
// 使用预定义的调度配置转换
|
||||
var schedule = "Daily"; // 或其他预定义值
|
||||
var cronExpression = SchedulerManager.ConvertScheduleToCron(schedule);
|
||||
bool success = await schedulerManager.AddScheduledTaskAsync(scriptGroup, cronExpression);
|
||||
```
|
||||
|
||||
### 任务管理
|
||||
|
||||
```csharp
|
||||
// 查看所有定时任务
|
||||
var tasks = await schedulerManager.GetAllScheduledTasksAsync();
|
||||
|
||||
// 删除特定任务
|
||||
bool success = await schedulerManager.RemoveScheduledTaskAsync("TaskName");
|
||||
|
||||
// 暂停任务
|
||||
bool success = await schedulerManager.PauseScheduledTaskAsync("TaskName");
|
||||
|
||||
// 恢复任务
|
||||
bool success = await schedulerManager.ResumeScheduledTaskAsync("TaskName");
|
||||
|
||||
// 更新任务的Cron表达式
|
||||
bool success = await schedulerManager.UpdateScheduledTaskAsync("TaskName", "0 0 10 * * ? *");
|
||||
```
|
||||
|
||||
### 批量操作
|
||||
|
||||
```csharp
|
||||
// 批量添加多个脚本组的定时任务
|
||||
var scriptGroups = GetAllScriptGroups();
|
||||
int successCount = await dynamicTaskService.AddMultipleScriptGroupSchedulesAsync(scriptGroups);
|
||||
|
||||
// 删除指定脚本组的所有任务
|
||||
int removedCount = await dynamicTaskService.RemoveScriptGroupSchedulesAsync("ScriptGroupName");
|
||||
|
||||
// 清理所有定时任务
|
||||
int clearedCount = await dynamicTaskService.ClearAllScheduledTasksAsync();
|
||||
```
|
||||
|
||||
### 任务报告
|
||||
|
||||
```csharp
|
||||
// 获取定时任务报告
|
||||
var report = await dynamicTaskService.GetScheduledTaskReportAsync();
|
||||
Console.WriteLine($"总任务数: {report.TotalTasks}");
|
||||
Console.WriteLine($"活跃任务数: {report.ActiveTasks}");
|
||||
|
||||
// 查看即将执行的任务
|
||||
foreach (var execution in report.NextExecutions)
|
||||
{
|
||||
Console.WriteLine($"{execution.ScriptGroupName} 将在 {execution.NextFireTime} 执行");
|
||||
}
|
||||
```
|
||||
|
||||
## UI 集成
|
||||
|
||||
在 `ScriptControlViewModel` 中新增了以下命令,可以在界面中使用:
|
||||
|
||||
1. **OnAddScheduledTaskAsync** - 添加定时任务
|
||||
2. **OnViewScheduledTasksAsync** - 查看所有定时任务
|
||||
3. **OnRemoveScheduledTasksAsync** - 删除定时任务
|
||||
4. **OnViewScheduledTaskReportAsync** - 查看任务报告
|
||||
5. **OnBatchAddScheduledTasksAsync** - 批量添加任务
|
||||
|
||||
## Cron 表达式说明
|
||||
|
||||
| 表达式 | 含义 |
|
||||
|--------|------|
|
||||
| `0 0 0 * * ? *` | 每天午夜执行 |
|
||||
| `0 0 8 * * ? *` | 每天上午8点执行 |
|
||||
| `0 30 9 ? * MON-FRI *` | 工作日上午9:30执行 |
|
||||
| `0 0 0 ? * MON *` | 每周一午夜执行 |
|
||||
| `0 0 0 1 * ? *` | 每月1号午夜执行 |
|
||||
| `0 0/30 * * * ? *` | 每30分钟执行 |
|
||||
| `0 0 8,12,18 * * ? *` | 每天8点、12点、18点执行 |
|
||||
|
||||
## 预定义调度配置转换
|
||||
|
||||
现有的调度配置会自动转换为相应的 Cron 表达式:
|
||||
|
||||
- `Daily` → `0 0 0 * * ? *` (每天午夜)
|
||||
- `EveryTwoDays` → `0 0 0 1/2 * ? *` (每两天)
|
||||
- `Monday` → `0 0 0 ? * MON *` (每周一)
|
||||
- `Tuesday` → `0 0 0 ? * TUE *` (每周二)
|
||||
- 其他自定义值被视为 Cron 表达式直接使用
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **任务执行环境**:定时任务在后台线程中执行,请确保脚本兼容性
|
||||
2. **错误处理**:任务执行失败不会影响其他任务的调度
|
||||
3. **性能考虑**:避免添加过于频繁的定时任务
|
||||
4. **数据持久化**:当前使用内存存储,应用重启后需要重新添加任务
|
||||
5. **并发控制**:默认最大并发数为10,可在配置中调整
|
||||
|
||||
## 扩展开发
|
||||
|
||||
### 自定义任务类型
|
||||
|
||||
```csharp
|
||||
// 创建自定义任务
|
||||
public class CustomScriptJob : IJob
|
||||
{
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
// 自定义任务逻辑
|
||||
}
|
||||
}
|
||||
|
||||
// 注册自定义任务
|
||||
services.AddQuartz(q =>
|
||||
{
|
||||
q.UseInMemoryStore();
|
||||
// 添加自定义任务
|
||||
q.AddJob<CustomScriptJob>(opts => opts.WithIdentity("CustomJob"));
|
||||
});
|
||||
```
|
||||
|
||||
### 数据持久化
|
||||
|
||||
如需任务数据持久化,可配置数据库存储:
|
||||
|
||||
```csharp
|
||||
services.AddQuartz(q =>
|
||||
{
|
||||
// 使用数据库存储替代内存存储
|
||||
q.UsePersistentStore(s =>
|
||||
{
|
||||
s.UseProperties = true;
|
||||
s.UseSqlServer("ConnectionString");
|
||||
s.UseJsonSerializer();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **服务未初始化**:确保在 `App.xaml.cs` 中正确注册了 Quartz.NET 服务
|
||||
2. **任务不执行**:检查 Cron 表达式格式是否正确
|
||||
3. **脚本组数据错误**:确保脚本组包含有效的项目且状态为启用
|
||||
4. **并发冲突**:避免同时运行多个相同的脚本组任务
|
||||
|
||||
### 日志监控
|
||||
|
||||
所有任务执行都会记录详细日志,可通过日志文件查看:
|
||||
- 任务开始执行
|
||||
- 任务执行完成
|
||||
- 任务执行异常
|
||||
- 调度器状态变化
|
||||
|
||||
## 总结
|
||||
|
||||
Quartz.NET 集成为 Better Genshin Impact 提供了强大的定时任务功能,通过简洁的 API 和友好的 UI 界面,用户可以轻松实现脚本组的自动化调度执行。该集成保持了原有功能的完整性,同时扩展了应用的自动化能力。
|
||||
274
BetterGenshinImpact/Service/Quartz/SchedulerManager.cs
Normal file
274
BetterGenshinImpact/Service/Quartz/SchedulerManager.cs
Normal file
@@ -0,0 +1,274 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Quartz;
|
||||
|
||||
namespace BetterGenshinImpact.Service.Quartz;
|
||||
|
||||
/// <summary>
|
||||
/// 调度管理器 - 管理动态添加和删除定时任务
|
||||
/// </summary>
|
||||
public class SchedulerManager
|
||||
{
|
||||
private readonly IScheduler _scheduler;
|
||||
private readonly ILogger<SchedulerManager> _logger;
|
||||
|
||||
public SchedulerManager(IScheduler scheduler, ILogger<SchedulerManager> logger)
|
||||
{
|
||||
_scheduler = scheduler;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态添加定时任务
|
||||
/// </summary>
|
||||
/// <param name="scriptGroup">脚本组</param>
|
||||
/// <param name="cronExpression">Cron表达式</param>
|
||||
/// <param name="jobName">任务名称(可选)</param>
|
||||
/// <returns>任务是否添加成功</returns>
|
||||
public async Task<bool> AddScheduledTaskAsync(ScriptGroup scriptGroup, string cronExpression, string? jobName = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
jobName ??= $"ScriptGroup_{scriptGroup.Name}_{DateTime.Now.Ticks}";
|
||||
var jobKey = new JobKey(jobName, "ScriptGroup");
|
||||
var triggerKey = new TriggerKey($"{jobName}_trigger", "ScriptGroup");
|
||||
|
||||
// 检查任务是否已存在
|
||||
if (await _scheduler.CheckExists(jobKey))
|
||||
{
|
||||
_logger.LogWarning("任务 {JobName} 已存在", jobName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建任务
|
||||
var job = JobBuilder.Create<ScriptExecutionJob>()
|
||||
.WithIdentity(jobKey)
|
||||
.UsingJobData("ScriptGroupName", scriptGroup.Name)
|
||||
.UsingJobData("ScriptGroupData", scriptGroup.ToJson())
|
||||
.WithDescription($"脚本组 {scriptGroup.Name} 的定时任务")
|
||||
.Build();
|
||||
|
||||
// 创建触发器
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.WithIdentity(triggerKey)
|
||||
.WithCronSchedule(cronExpression)
|
||||
.WithDescription($"脚本组 {scriptGroup.Name} 的定时触发器")
|
||||
.Build();
|
||||
|
||||
// 添加任务到调度器
|
||||
await _scheduler.ScheduleJob(job, trigger);
|
||||
|
||||
_logger.LogInformation("成功添加定时任务:{JobName},Cron表达式:{CronExpression}", jobName, cronExpression);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "添加定时任务失败:{Message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除定时任务
|
||||
/// </summary>
|
||||
/// <param name="jobName">任务名称</param>
|
||||
/// <returns>任务是否删除成功</returns>
|
||||
public async Task<bool> RemoveScheduledTaskAsync(string jobName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobKey = new JobKey(jobName, "ScriptGroup");
|
||||
|
||||
if (!await _scheduler.CheckExists(jobKey))
|
||||
{
|
||||
_logger.LogWarning("任务 {JobName} 不存在", jobName);
|
||||
return false;
|
||||
}
|
||||
|
||||
await _scheduler.DeleteJob(jobKey);
|
||||
_logger.LogInformation("成功删除定时任务:{JobName}", jobName);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "删除定时任务失败:{Message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有定时任务
|
||||
/// </summary>
|
||||
/// <returns>任务列表</returns>
|
||||
public async Task<List<ScheduledTaskInfo>> GetAllScheduledTasksAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobKeys = await _scheduler.GetJobKeys(GroupMatcher<JobKey>.GroupEquals("ScriptGroup"));
|
||||
var tasks = new List<ScheduledTaskInfo>();
|
||||
|
||||
foreach (var jobKey in jobKeys)
|
||||
{
|
||||
var jobDetail = await _scheduler.GetJobDetail(jobKey);
|
||||
var triggers = await _scheduler.GetTriggersOfJob(jobKey);
|
||||
|
||||
foreach (var trigger in triggers)
|
||||
{
|
||||
var nextFireTime = trigger.GetNextFireTimeUtc();
|
||||
var previousFireTime = trigger.GetPreviousFireTimeUtc();
|
||||
|
||||
tasks.Add(new ScheduledTaskInfo
|
||||
{
|
||||
JobName = jobKey.Name,
|
||||
ScriptGroupName = jobDetail?.JobDataMap.GetString("ScriptGroupName") ?? "",
|
||||
CronExpression = trigger is ICronTrigger cronTrigger ? cronTrigger.CronExpressionString : "",
|
||||
NextFireTime = nextFireTime?.DateTime,
|
||||
PreviousFireTime = previousFireTime?.DateTime,
|
||||
Description = jobDetail?.Description ?? ""
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return tasks;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取定时任务列表失败:{Message}", ex.Message);
|
||||
return new List<ScheduledTaskInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新定时任务的Cron表达式
|
||||
/// </summary>
|
||||
/// <param name="jobName">任务名称</param>
|
||||
/// <param name="newCronExpression">新的Cron表达式</param>
|
||||
/// <returns>任务是否更新成功</returns>
|
||||
public async Task<bool> UpdateScheduledTaskAsync(string jobName, string newCronExpression)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobKey = new JobKey(jobName, "ScriptGroup");
|
||||
var triggerKey = new TriggerKey($"{jobName}_trigger", "ScriptGroup");
|
||||
|
||||
if (!await _scheduler.CheckExists(jobKey))
|
||||
{
|
||||
_logger.LogWarning("任务 {JobName} 不存在", jobName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建新的触发器
|
||||
var newTrigger = TriggerBuilder.Create()
|
||||
.WithIdentity(triggerKey)
|
||||
.WithCronSchedule(newCronExpression)
|
||||
.WithDescription($"更新的定时触发器")
|
||||
.Build();
|
||||
|
||||
// 重新调度任务
|
||||
await _scheduler.RescheduleJob(triggerKey, newTrigger);
|
||||
|
||||
_logger.LogInformation("成功更新定时任务:{JobName},新的Cron表达式:{CronExpression}", jobName, newCronExpression);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "更新定时任务失败:{Message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暂停定时任务
|
||||
/// </summary>
|
||||
/// <param name="jobName">任务名称</param>
|
||||
/// <returns>任务是否暂停成功</returns>
|
||||
public async Task<bool> PauseScheduledTaskAsync(string jobName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobKey = new JobKey(jobName, "ScriptGroup");
|
||||
|
||||
if (!await _scheduler.CheckExists(jobKey))
|
||||
{
|
||||
_logger.LogWarning("任务 {JobName} 不存在", jobName);
|
||||
return false;
|
||||
}
|
||||
|
||||
await _scheduler.PauseJob(jobKey);
|
||||
_logger.LogInformation("成功暂停定时任务:{JobName}", jobName);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "暂停定时任务失败:{Message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 恢复定时任务
|
||||
/// </summary>
|
||||
/// <param name="jobName">任务名称</param>
|
||||
/// <returns>任务是否恢复成功</returns>
|
||||
public async Task<bool> ResumeScheduledTaskAsync(string jobName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobKey = new JobKey(jobName, "ScriptGroup");
|
||||
|
||||
if (!await _scheduler.CheckExists(jobKey))
|
||||
{
|
||||
_logger.LogWarning("任务 {JobName} 不存在", jobName);
|
||||
return false;
|
||||
}
|
||||
|
||||
await _scheduler.ResumeJob(jobKey);
|
||||
_logger.LogInformation("成功恢复定时任务:{JobName}", jobName);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "恢复定时任务失败:{Message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据脚本组配置转换为Cron表达式
|
||||
/// </summary>
|
||||
/// <param name="schedule">调度配置</param>
|
||||
/// <returns>Cron表达式</returns>
|
||||
public static string ConvertScheduleToCron(string schedule)
|
||||
{
|
||||
return schedule switch
|
||||
{
|
||||
"Daily" => "0 0 0 * * ? *", // 每天午夜执行
|
||||
"EveryTwoDays" => "0 0 0 1/2 * ? *", // 每两天执行
|
||||
"Monday" => "0 0 0 ? * MON *", // 每周一执行
|
||||
"Tuesday" => "0 0 0 ? * TUE *", // 每周二执行
|
||||
"Wednesday" => "0 0 0 ? * WED *", // 每周三执行
|
||||
"Thursday" => "0 0 0 ? * THU *", // 每周四执行
|
||||
"Friday" => "0 0 0 ? * FRI *", // 每周五执行
|
||||
"Saturday" => "0 0 0 ? * SAT *", // 每周六执行
|
||||
"Sunday" => "0 0 0 ? * SUN *", // 每周日执行
|
||||
_ => schedule // 假设是自定义Cron表达式
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定时任务信息
|
||||
/// </summary>
|
||||
public class ScheduledTaskInfo
|
||||
{
|
||||
public string JobName { get; set; } = "";
|
||||
public string ScriptGroupName { get; set; } = "";
|
||||
public string CronExpression { get; set; } = "";
|
||||
public DateTime? NextFireTime { get; set; }
|
||||
public DateTime? PreviousFireTime { get; set; }
|
||||
public string Description { get; set; } = "";
|
||||
}
|
||||
67
BetterGenshinImpact/Service/Quartz/ScriptExecutionJob.cs
Normal file
67
BetterGenshinImpact/Service/Quartz/ScriptExecutionJob.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
using BetterGenshinImpact.Service.Interface;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Quartz;
|
||||
|
||||
namespace BetterGenshinImpact.Service.Quartz;
|
||||
|
||||
/// <summary>
|
||||
/// 脚本执行任务 - 用于 Quartz.NET 调度执行
|
||||
/// </summary>
|
||||
public class ScriptExecutionJob : IJob
|
||||
{
|
||||
private readonly ILogger<ScriptExecutionJob> _logger;
|
||||
private readonly IScriptService _scriptService;
|
||||
|
||||
public ScriptExecutionJob(ILogger<ScriptExecutionJob> logger, IScriptService scriptService)
|
||||
{
|
||||
_logger = logger;
|
||||
_scriptService = scriptService;
|
||||
}
|
||||
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var jobDataMap = context.JobDetail.JobDataMap;
|
||||
var scriptGroupName = jobDataMap.GetString("ScriptGroupName");
|
||||
var scriptGroupData = jobDataMap.GetString("ScriptGroupData");
|
||||
|
||||
if (string.IsNullOrEmpty(scriptGroupName) || string.IsNullOrEmpty(scriptGroupData))
|
||||
{
|
||||
_logger.LogError("任务执行失败:缺少必要的参数 ScriptGroupName 或 ScriptGroupData");
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("开始执行定时任务:{ScriptGroupName}", scriptGroupName);
|
||||
|
||||
// 反序列化脚本组数据
|
||||
var scriptGroup = ScriptGroup.FromJson(scriptGroupData);
|
||||
if (scriptGroup == null)
|
||||
{
|
||||
_logger.LogError("任务执行失败:无法反序列化脚本组数据");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取有效的项目列表
|
||||
var enabledProjects = scriptGroup.Projects.Where(p => p.Status == "Enabled").ToList();
|
||||
if (enabledProjects.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("脚本组 {ScriptGroupName} 没有启用的项目", scriptGroupName);
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行脚本组
|
||||
await _scriptService.RunMulti(enabledProjects, scriptGroupName, null);
|
||||
|
||||
_logger.LogInformation("定时任务执行完成:{ScriptGroupName}", scriptGroupName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "定时任务执行异常:{Message}", ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using BetterGenshinImpact.Core.Config;
|
||||
using BetterGenshinImpact.Core.Script;
|
||||
using BetterGenshinImpact.Core.Script.Group;
|
||||
@@ -51,6 +52,10 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
|
||||
private readonly IScriptService _scriptService;
|
||||
|
||||
private readonly Service.Quartz.SchedulerManager? _schedulerManager;
|
||||
|
||||
private readonly Service.Quartz.DynamicTaskExampleService? _dynamicTaskService;
|
||||
|
||||
/// <summary>
|
||||
/// 配置组配置
|
||||
/// </summary>
|
||||
@@ -76,6 +81,8 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
{
|
||||
_snackbarService = snackbarService;
|
||||
_scriptService = scriptService;
|
||||
_schedulerManager = App.GetService<Service.Quartz.SchedulerManager>();
|
||||
_dynamicTaskService = App.GetService<Service.Quartz.DynamicTaskExampleService>();
|
||||
ScriptGroups.CollectionChanged += ScriptGroupsCollectionChanged;
|
||||
}
|
||||
|
||||
@@ -1712,7 +1719,489 @@ public partial class ScriptControlViewModel : ViewModel
|
||||
{
|
||||
RunnerContext.Instance.Reset();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#region Quartz.NET 动态任务管理示例
|
||||
|
||||
/// <summary>
|
||||
/// 为当前选中的脚本组添加定时任务
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
public async Task OnAddScheduledTaskAsync()
|
||||
{
|
||||
if (SelectedScriptGroup == null)
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"未选择配置组",
|
||||
"请先选择一个配置组",
|
||||
ControlAppearance.Caution,
|
||||
null,
|
||||
TimeSpan.FromSeconds(2)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_dynamicTaskService == null)
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"服务未初始化",
|
||||
"Quartz.NET 服务未正确初始化",
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(2)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 创建输入对话框
|
||||
var stackPanel = new StackPanel();
|
||||
|
||||
// Cron表达式输入
|
||||
var cronLabel = new TextBlock { Text = "Cron表达式:", Margin = new Thickness(0, 0, 0, 5) };
|
||||
var cronTextBox = new TextBox
|
||||
{
|
||||
Text = "0 0 0 * * ? *", // 默认每天午夜执行
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
};
|
||||
|
||||
// 预设选项
|
||||
var presetLabel = new TextBlock { Text = "或选择预设:", Margin = new Thickness(0, 0, 0, 5) };
|
||||
var presetComboBox = new ComboBox
|
||||
{
|
||||
ItemsSource = new[]
|
||||
{
|
||||
new { Text = "每天执行", Value = "0 0 0 * * ? *" },
|
||||
new { Text = "每小时执行", Value = "0 0 * * * ? *" },
|
||||
new { Text = "每30分钟执行", Value = "0 0/30 * * * ? *" },
|
||||
new { Text = "每周一执行", Value = "0 0 0 ? * MON *" },
|
||||
new { Text = "每月1号执行", Value = "0 0 0 1 * ? *" },
|
||||
new { Text = "工作日执行", Value = "0 0 0 ? * MON-FRI *" }
|
||||
},
|
||||
DisplayMemberPath = "Text",
|
||||
SelectedValuePath = "Value",
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
};
|
||||
|
||||
presetComboBox.SelectionChanged += (s, e) =>
|
||||
{
|
||||
if (presetComboBox.SelectedValue != null)
|
||||
{
|
||||
cronTextBox.Text = presetComboBox.SelectedValue.ToString();
|
||||
}
|
||||
};
|
||||
|
||||
stackPanel.Children.Add(cronLabel);
|
||||
stackPanel.Children.Add(cronTextBox);
|
||||
stackPanel.Children.Add(presetLabel);
|
||||
stackPanel.Children.Add(presetComboBox);
|
||||
|
||||
var uiMessageBox = new Wpf.Ui.Controls.MessageBox
|
||||
{
|
||||
Title = "添加定时任务",
|
||||
Content = stackPanel,
|
||||
CloseButtonText = "取消",
|
||||
PrimaryButtonText = "添加",
|
||||
Owner = Application.Current.MainWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
};
|
||||
|
||||
var result = await uiMessageBox.ShowDialogAsync();
|
||||
if (result == MessageBoxResult.Primary)
|
||||
{
|
||||
var cronExpression = cronTextBox.Text?.Trim();
|
||||
if (string.IsNullOrEmpty(cronExpression))
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"参数错误",
|
||||
"请输入有效的Cron表达式",
|
||||
ControlAppearance.Caution,
|
||||
null,
|
||||
TimeSpan.FromSeconds(2)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = await _dynamicTaskService.AddScriptGroupScheduleAsync(SelectedScriptGroup, cronExpression);
|
||||
if (success)
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"任务添加成功",
|
||||
$"已为配置组 {SelectedScriptGroup.Name} 添加定时任务",
|
||||
ControlAppearance.Success,
|
||||
null,
|
||||
TimeSpan.FromSeconds(3)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"任务添加失败",
|
||||
"请检查Cron表达式是否正确",
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(3)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "添加定时任务失败");
|
||||
_snackbarService.Show(
|
||||
"添加定时任务失败",
|
||||
ex.Message,
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(3)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查看所有定时任务
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
public async Task OnViewScheduledTasksAsync()
|
||||
{
|
||||
if (_schedulerManager == null)
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"服务未初始化",
|
||||
"Quartz.NET 服务未正确初始化",
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(2)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var tasks = await _schedulerManager.GetAllScheduledTasksAsync();
|
||||
|
||||
if (tasks.Count == 0)
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"没有定时任务",
|
||||
"当前没有配置任何定时任务",
|
||||
ControlAppearance.Info,
|
||||
null,
|
||||
TimeSpan.FromSeconds(2)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建任务列表显示
|
||||
var stackPanel = new StackPanel();
|
||||
|
||||
foreach (var task in tasks.OrderBy(t => t.ScriptGroupName))
|
||||
{
|
||||
var taskPanel = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
Background = new SolidColorBrush(Colors.LightGray) { Opacity = 0.1 }
|
||||
};
|
||||
|
||||
taskPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"任务名称: {task.JobName}",
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(5, 5, 5, 0)
|
||||
});
|
||||
|
||||
taskPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"脚本组: {task.ScriptGroupName}",
|
||||
Margin = new Thickness(5, 0, 5, 0)
|
||||
});
|
||||
|
||||
taskPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"Cron表达式: {task.CronExpression}",
|
||||
Margin = new Thickness(5, 0, 5, 0)
|
||||
});
|
||||
|
||||
if (task.NextFireTime.HasValue)
|
||||
{
|
||||
taskPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"下次执行: {task.NextFireTime:yyyy-MM-dd HH:mm:ss}",
|
||||
Margin = new Thickness(5, 0, 5, 5)
|
||||
});
|
||||
}
|
||||
|
||||
stackPanel.Children.Add(taskPanel);
|
||||
}
|
||||
|
||||
var uiMessageBox = new Wpf.Ui.Controls.MessageBox
|
||||
{
|
||||
Title = $"定时任务列表 ({tasks.Count} 个任务)",
|
||||
Content = new ScrollViewer
|
||||
{
|
||||
Content = stackPanel,
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||
Height = 400,
|
||||
Width = 600
|
||||
},
|
||||
CloseButtonText = "关闭",
|
||||
Owner = Application.Current.MainWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
};
|
||||
|
||||
await uiMessageBox.ShowDialogAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "查看定时任务失败");
|
||||
_snackbarService.Show(
|
||||
"查看定时任务失败",
|
||||
ex.Message,
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(3)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除定时任务
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
public async Task OnRemoveScheduledTasksAsync()
|
||||
{
|
||||
if (SelectedScriptGroup == null)
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"未选择配置组",
|
||||
"请先选择一个配置组",
|
||||
ControlAppearance.Caution,
|
||||
null,
|
||||
TimeSpan.FromSeconds(2)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_dynamicTaskService == null)
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"服务未初始化",
|
||||
"Quartz.NET 服务未正确初始化",
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(2)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
$"确定要删除配置组 {SelectedScriptGroup.Name} 的所有定时任务吗?",
|
||||
"删除定时任务",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question
|
||||
);
|
||||
|
||||
if (result == System.Windows.MessageBoxResult.Yes)
|
||||
{
|
||||
int removedCount = await _dynamicTaskService.RemoveScriptGroupSchedulesAsync(SelectedScriptGroup.Name);
|
||||
|
||||
_snackbarService.Show(
|
||||
"删除完成",
|
||||
$"成功删除 {removedCount} 个定时任务",
|
||||
ControlAppearance.Success,
|
||||
null,
|
||||
TimeSpan.FromSeconds(3)
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "删除定时任务失败");
|
||||
_snackbarService.Show(
|
||||
"删除定时任务失败",
|
||||
ex.Message,
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(3)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查看定时任务报告
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
public async Task OnViewScheduledTaskReportAsync()
|
||||
{
|
||||
if (_dynamicTaskService == null)
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"服务未初始化",
|
||||
"Quartz.NET 服务未正确初始化",
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(2)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var report = await _dynamicTaskService.GetScheduledTaskReportAsync();
|
||||
|
||||
var stackPanel = new StackPanel();
|
||||
|
||||
// 总览信息
|
||||
stackPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "定时任务总览",
|
||||
FontSize = 16,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
});
|
||||
|
||||
stackPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"总任务数: {report.TotalTasks}",
|
||||
Margin = new Thickness(0, 0, 0, 5)
|
||||
});
|
||||
|
||||
stackPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"活跃任务数: {report.ActiveTasks}",
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
});
|
||||
|
||||
// 按脚本组分组
|
||||
if (report.TasksByScriptGroup.Any())
|
||||
{
|
||||
stackPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "按脚本组分组:",
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 0, 0, 5)
|
||||
});
|
||||
|
||||
foreach (var group in report.TasksByScriptGroup)
|
||||
{
|
||||
stackPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $" {group.Key}: {group.Value} 个任务",
|
||||
Margin = new Thickness(10, 0, 0, 2)
|
||||
});
|
||||
}
|
||||
|
||||
stackPanel.Children.Add(new TextBlock { Text = "", Margin = new Thickness(0, 0, 0, 5) });
|
||||
}
|
||||
|
||||
// 即将执行的任务
|
||||
if (report.NextExecutions.Any())
|
||||
{
|
||||
stackPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "即将执行的任务:",
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 0, 0, 5)
|
||||
});
|
||||
|
||||
foreach (var execution in report.NextExecutions)
|
||||
{
|
||||
stackPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $" {execution.ScriptGroupName} - {execution.NextFireTime:yyyy-MM-dd HH:mm:ss}",
|
||||
Margin = new Thickness(10, 0, 0, 2)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var uiMessageBox = new Wpf.Ui.Controls.MessageBox
|
||||
{
|
||||
Title = "定时任务报告",
|
||||
Content = new ScrollViewer
|
||||
{
|
||||
Content = stackPanel,
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||
Height = 400,
|
||||
Width = 500
|
||||
},
|
||||
CloseButtonText = "关闭",
|
||||
Owner = Application.Current.MainWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner,
|
||||
};
|
||||
|
||||
await uiMessageBox.ShowDialogAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "查看定时任务报告失败");
|
||||
_snackbarService.Show(
|
||||
"查看定时任务报告失败",
|
||||
ex.Message,
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(3)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量添加定时任务示例
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
public async Task OnBatchAddScheduledTasksAsync()
|
||||
{
|
||||
if (_dynamicTaskService == null)
|
||||
{
|
||||
_snackbarService.Show(
|
||||
"服务未初始化",
|
||||
"Quartz.NET 服务未正确初始化",
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(2)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"这将为所有启用的脚本组添加定时任务(每天午夜执行)。确定继续吗?",
|
||||
"批量添加定时任务",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question
|
||||
);
|
||||
|
||||
if (result == System.Windows.MessageBoxResult.Yes)
|
||||
{
|
||||
var enabledScriptGroups = ScriptGroups.Where(sg =>
|
||||
sg.Projects.Any(p => p.Status == "Enabled")).ToList();
|
||||
|
||||
int successCount = await _dynamicTaskService.AddMultipleScriptGroupSchedulesAsync(enabledScriptGroups);
|
||||
|
||||
_snackbarService.Show(
|
||||
"批量添加完成",
|
||||
$"成功添加 {successCount}/{enabledScriptGroups.Count} 个定时任务",
|
||||
ControlAppearance.Success,
|
||||
null,
|
||||
TimeSpan.FromSeconds(3)
|
||||
);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "批量添加定时任务失败");
|
||||
_snackbarService.Show(
|
||||
"批量添加定时任务失败",
|
||||
ex.Message,
|
||||
ControlAppearance.Danger,
|
||||
null,
|
||||
TimeSpan.FromSeconds(3)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user