集成定时任务框架和配置定时策略

This commit is contained in:
huangxianguo 2024-09-13 18:44:13 +08:00
parent 7ef948ae48
commit 57ea062060
24 changed files with 438 additions and 203 deletions

View File

@ -0,0 +1,11 @@
namespace MasstransferCommon.Annotation;
/// <summary>
/// 通过添加特性注解实现开启定时任务
/// </summary>
/// <param name="cron"></param>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class ScheduledJobAttribute(string cron) : Attribute
{
public string Cron { get; } = cron;
}

View File

@ -0,0 +1,57 @@
using System.Reflection;
using MasstransferCommon.Annotation;
using MasstransferCommon.Utils;
using Quartz;
using Quartz.Impl;
namespace MasstransferCommon.Scheduler;
/// <summary>
/// 定时任务器
/// </summary>
public class QuartzScheduler
{
private readonly IScheduler _scheduler;
private QuartzScheduler()
{
var schedulerFactory = new StdSchedulerFactory();
_scheduler = schedulerFactory.GetScheduler().Result;
}
public static QuartzScheduler Instance { get; } = new();
/// <summary>
/// 启动定时任务器
/// </summary>
public async Task StartAsync()
{
await _scheduler.Start();
// 查找所有带有 ScheduledJobAttribute 的类
var jobs = AssemblyUtil.GetTypesByAttribute(typeof(ScheduledJobAttribute));
foreach (var job in jobs)
{
var attribute = job.GetCustomAttribute<ScheduledJobAttribute>();
var jobDetail = JobBuilder.Create(job)
.WithIdentity(job.Name)
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity($"{job.Name}.trigger")
.WithCronSchedule(attribute!.Cron)
.Build();
await _scheduler.ScheduleJob(jobDetail, trigger);
}
}
/// <summary>
/// 停止定时器
/// </summary>
public async Task StopAsync()
{
await _scheduler.Shutdown();
}
}

View File

@ -1,98 +0,0 @@
using Quartz;
using Quartz.Impl;
using Serilog;
namespace MasstransferCommon.scheduler;
public class SchedulerHelper
{
private static readonly Lazy<IScheduler> lazyScheduler = new(() => InitSchedulerAsync().GetAwaiter().GetResult());
private static readonly Dictionary<string, IJobDetail> _jobDetails = new();
private static IScheduler Scheduler => lazyScheduler.Value;
private static async Task<IScheduler> InitSchedulerAsync()
{
try
{
return await new StdSchedulerFactory().GetScheduler();
}
catch (Exception ex)
{
Log.Error($"Failed to initialize scheduler: {ex.Message}");
throw;
}
}
public static async Task Start()
{
await Scheduler.Start();
}
public static async Task SchedulerInterval<T>(Dictionary<string, object>? data, int interval,
string group = "defaultGroup") where T : IJob
{
var job = CreateJob<T>(data, group);
var trigger = TriggerBuilder.Create()
.WithIdentity(typeof(T).Name, group)
.StartNow()
.WithSimpleSchedule(x => x.WithIntervalInSeconds(interval).RepeatForever())
.Build();
await Scheduler.ScheduleJob(job, trigger);
}
public static async Task SchedulerCorn<T>(Dictionary<string, object>? data, string? cronExpression,
string group = "defaultGroup") where T : IJob
{
var job = CreateJob<T>(data, group);
var trigger = TriggerBuilder.Create()
.WithIdentity(typeof(T).Name, group)
.StartNow()
.WithSchedule(CronScheduleBuilder.CronSchedule(cronExpression))
.Build();
await Scheduler.ScheduleJob(job, trigger);
}
private static IJobDetail CreateJob<T>(Dictionary<string, object>? data, string group) where T : IJob
{
if (_jobDetails.ContainsKey(typeof(T).Name)) return _jobDetails[typeof(T).Name];
var job = JobBuilder.Create<T>()
.WithIdentity(typeof(T).Name, group)
.Build();
if (data != null && data.Count > 0)
foreach (var item in data)
job.JobDataMap.Add(item.Key, item.Value);
_jobDetails[typeof(T).Name] = job;
return job;
}
public static async Task PauseJob<T>(string group = "defaultGroup")
{
if (_jobDetails.ContainsKey(typeof(T).Name)) await Scheduler.PauseJob(JobKey.Create(typeof(T).Name, group));
}
public static async Task ResumeJob<T>(string group = "defaultGroup")
{
if (_jobDetails.ContainsKey(typeof(T).Name)) await Scheduler.ResumeJob(JobKey.Create(typeof(T).Name, group));
}
public static async Task Shutdown()
{
if (!Scheduler.IsShutdown) await Scheduler.Shutdown();
}
public static async Task TriggerOnceImmediately<T>(string group = "defaultGroup") where T : IJob
{
if (!_jobDetails.ContainsKey(typeof(T).Name)) return;
await Scheduler.TriggerJob(new JobKey(typeof(T).Name, group));
}
}

View File

@ -0,0 +1,49 @@
using System.Reflection;
namespace MasstransferCommon.Utils;
/// <summary>
/// 包含程序集相关的工具类
/// </summary>
public class AssemblyUtil
{
private const string Token = "masstransfer";
/// <summary>
/// 根据注解来获取类信息
/// </summary>
/// <param name="attributeType"></param>
/// <returns></returns>
public static List<Type> GetTypesByAttribute(Type attributeType)
{
return GetAssemblies()
.SelectMany(a => a.GetTypes()
.Where(t => t.GetCustomAttributes(attributeType, true).Length > 0))
.ToList();
}
private static IEnumerable<Assembly> GetAssemblies()
{
var assemblies = new List<Assembly>();
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var name = assembly.GetName().Name;
if (name != null && name.ToLower().Contains(Token)) GetReferenceAssemblies(assembly, assemblies);
}
return assemblies;
}
private static void GetReferenceAssemblies(Assembly assembly, ICollection<Assembly> assemblies)
{
foreach (var assemblyName in assembly.GetReferencedAssemblies())
{
var name = assemblyName.Name;
if (name == null || !name.ToLower().Contains(Token)) continue;
var ass = Assembly.Load(assemblyName);
if (assemblies.Contains(ass)) continue;
assemblies.Add(ass);
GetReferenceAssemblies(ass, assemblies);
}
}
}

View File

@ -52,8 +52,6 @@ public class FormulaService
var pcbSetting = formulaDto.PcbSetting;
var waferSetting = formulaDto.WaferSetting;
var jobSetting = formulaDto.JobSetting;
var axisPositionParams = formulaDto.AxisPositionParams;
var axisVariables = formulaDto.AxisVariables;
if (algorithmParams != null)
{
@ -120,28 +118,6 @@ public class FormulaService
Db.SaveOrUpdate(jobSetting);
}
if (axisPositionParams != null)
{
var axisPositionParamsByFormulaId = GetAxisPositionParamsByFormulaId(formulaId);
if (axisPositionParamsByFormulaId != null)
{
axisPositionParams.Id = axisPositionParamsByFormulaId.Id;
}
Db.SaveOrUpdate(axisPositionParams);
}
if (axisVariables != null)
{
var axisVariablesByFormulaId = GetAxisVariablesByFormulaId(formulaId);
if (axisVariablesByFormulaId != null)
{
axisVariables.Id = axisVariablesByFormulaId.Id;
}
Db.SaveOrUpdate(axisVariables);
}
}
/// <summary>
@ -154,7 +130,7 @@ public class FormulaService
return BeanUtil.CopyProperties<FormulaDTO>(formulas);
}
private static Formula GetFormulaByName(string name)
private static Formula? GetFormulaByName(string name)
{
return Db.Query<Formula>("select * from formulas where name = ?", name).FirstOrDefault();
}
@ -173,9 +149,10 @@ public class FormulaService
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
private static FormulaDTO GetFormulaById(string id)
private static FormulaDTO? GetFormulaById(string id)
{
var formula = Db.GetById<Formula>(id);
if (formula == null) return null;
var dto = BeanUtil.CopyProperties<FormulaDTO>(formula);
dto.WorkBenchSetting = GetWorkBenchSettingByFormulaId(id) ?? new WorkBenchSetting();
dto.WaferSetting = GetWaferSettingByFormulaId(id) ?? new WaferSetting();
@ -183,9 +160,7 @@ public class FormulaService
dto.NeedleSetting = GetNeedleSettingByFormulaId(id) ?? new NeedleSetting();
dto.AltimetryParams = GetAltimetryParamsByFormulaId(id) ?? new AltimetryParams();
dto.JobSetting = GetJobSettingByFormulaId(id) ?? new JobSetting();
dto.AxisPositionParams = GetAxisPositionParamsByFormulaId(id) ?? new AxisPositionParams();
dto.AlgorithmParams = GetAlgorithmParamsByFormulaId(id) ?? new AlgorithmParams();
dto.AxisVariables = GetAxisVariablesByFormulaId(id) ?? new AxisVariables();
return dto;
}
@ -218,18 +193,6 @@ public class FormulaService
.FirstOrDefault();
}
private static AxisVariables? GetAxisVariablesByFormulaId(string formulaId)
{
return Db.Query<AxisVariables>("select * from axis_variables where formula_id = ?", formulaId)
.FirstOrDefault();
}
private static AxisPositionParams? GetAxisPositionParamsByFormulaId(string formulaId)
{
return Db.Query<AxisPositionParams>("select * from axis_position_params where formula_id = ?", formulaId)
.FirstOrDefault();
}
private static AlgorithmParams? GetAlgorithmParamsByFormulaId(string formulaId)
{
return Db.Query<AlgorithmParams>("select * from algorithm_params where formula_id = ?", formulaId)

View File

@ -11,10 +11,6 @@ namespace MasstransferExporter.DataExporter.Model;
[Table("axis_position_params"), Description("轴位置参数")]
public class AxisPositionParams : Entity
{
[Column("formula_id"), Description("配方Id"), Hide]
public string FormulaId { get; set; }
[Column("wafer_y_manual_position"), Description("晶环龙门Y手动位置"),
Property(Group = "WaferY", Variable = "stAxisArPos", Axis = 0, Index = 0)]
public double WaferYManualPosition { get; set; }

View File

@ -11,9 +11,6 @@ namespace MasstransferExporter.DataExporter.Model;
[Table("axis_variables"), Description("轴变量")]
public class AxisVariables : Entity
{
[Column("formula_id"), Description("配方Id"), Hide]
public string FormulaId { get; set; }
[Column("wafer_y_motor_left_soft_limit"), Description("晶圆Y轴左软限位"),
Property(Group = "SoftLimit", Variable = "stAxisSoftLimit", Axis = 0, Index = 0)]
public double WaferYMotorLeftSoftLimit { get; set; }

View File

@ -1,4 +1,5 @@
using System.ComponentModel;
using Newtonsoft.Json;
namespace MasstransferExporter.DataExporter.Model;
@ -7,13 +8,17 @@ namespace MasstransferExporter.DataExporter.Model;
/// </summary>
public class FormulaDTO
{
[Description("配方id")] public string Id { get; set; }
[Description("配方id"), JsonProperty("id")]
public string Id { get; set; }
[Description("配方名称")] public string Name { get; set; }
[Description("配方名称"), JsonProperty("name")]
public string Name { get; set; }
[Description("配方编号")] public string Code { get; set; }
[Description("配方编号"), JsonProperty("code")]
public string Code { get; set; }
[Description("配方描述")] public string Description { get; set; }
[Description("配方描述"), JsonProperty("description")]
public string Description { get; set; }
[Description("是否启用")] public bool Selected { get; set; }
@ -28,9 +33,5 @@ public class FormulaDTO
public AltimetryParams? AltimetryParams { get; set; }
public JobSetting? JobSetting { get; set; }
public AxisVariables? AxisVariables { get; set; }
public AxisPositionParams? AxisPositionParams { get; set; }
public AlgorithmParams? AlgorithmParams { get; set; }
}

View File

@ -20,4 +20,8 @@ public class SystemParamsDTO
public MinioParams? MinioParams { get; set; }
public MqttParams? MqttParams { get; set; }
public AxisVariables? AxisVariables { get; set; }
public AxisPositionParams? AxisPositionParams { get; set; }
}

View File

@ -17,7 +17,6 @@ public class SystemParamsService
/// <summary>
/// 处理下发的系统配置参数
/// </summary>
/// <param name="data"></param>
public static void HandleSystemParamsIssuedEvent(SystemParamsDTO dto)
{
if (dto.IsNullOrEmpty()) return;
@ -95,6 +94,30 @@ public class SystemParamsService
Db.SaveOrUpdate(waferCameraInternalParams);
}
if (dto?.AxisVariables != null)
{
var axisVariables = dto.AxisVariables;
var axisVariablesFromDb = GetAxisVariables();
if (axisVariablesFromDb != null)
{
axisVariables.Id = axisVariablesFromDb.Id;
}
Db.SaveOrUpdate(axisVariables);
}
if (dto?.AxisPositionParams != null)
{
var axisPositionParams = dto.AxisPositionParams;
var axisPositionParamsFromDb = GetAxisPositionParams();
if (axisPositionParamsFromDb != null)
{
axisPositionParams.Id = axisPositionParamsFromDb.Id;
}
Db.SaveOrUpdate(axisPositionParams);
}
if (dto?.SubstrateCameraInternalParams == null) return;
var substrateCameraInternalParams = dto.SubstrateCameraInternalParams;
@ -124,6 +147,8 @@ public class SystemParamsService
MqttParams = GetMqttConnectParams(),
ScannerParams = GetScannerParams(),
SystemParams = GetSystemParams(),
AxisVariables = GetAxisVariables(),
AxisPositionParams = GetAxisPositionParams()
};
var systemParams = new ConfigData<SystemParamsDTO>()
@ -134,7 +159,6 @@ public class SystemParamsService
};
await MessageQueueHelper.Publish(Topics.ReportConfigData, systemParams);
await MessageQueueHelper.Publish(Topics.ReportConfigData, systemParams);
}
catch (Exception e)
{
@ -199,4 +223,16 @@ public class SystemParamsService
return Db.Query<MqttParams>("select * from mqtt_params limit 1")
.FirstOrDefault();
}
private static AxisVariables? GetAxisVariables()
{
return Db.Query<AxisVariables>("select * from axis_variables limit 1")
.FirstOrDefault();
}
private static AxisPositionParams? GetAxisPositionParams()
{
return Db.Query<AxisPositionParams>("select * from axis_position_params limit 1")
.FirstOrDefault();
}
}

View File

@ -0,0 +1,25 @@
using MasstransferCommon.Annotation;
using MasstransferExporter.DataExporter;
using Serilog;
namespace MasstransferExporter.Jobs;
/// <summary>
/// 坐标文件上报定时任务
/// 每5分钟执行一次
/// </summary>
[ScheduledJob("0 0/5 * * * ? ")]
public class CoordinateJob : IJob
{
public void Execute()
{
try
{
CoordinateService.CoordinateExporter();
}
catch (Exception e)
{
Log.Error(e, "坐标文件上传任务执行异常");
}
}
}

View File

@ -0,0 +1,24 @@
using MasstransferCommon.Annotation;
using MasstransferExporter.DataExporter;
using Serilog;
namespace MasstransferExporter.Jobs;
/// <summary>
/// 配方上报任务
/// </summary>
[ScheduledJob("0 15 2 ? * *")]
public class FormulaJob : IJob
{
public async void Execute()
{
try
{
await FormulaService.FormulaDataExporter();
}
catch (Exception e)
{
Log.Error(e, "配方上报任务异常");
}
}
}

View File

@ -0,0 +1,24 @@
using MasstransferCommon.Annotation;
using MasstransferExporter.StatExporter;
using Serilog;
namespace MasstransferExporter.Jobs;
/// <summary>
/// 每 10 秒上报一次心跳
/// </summary>
[ScheduledJob("0/10 * * * * ? *")]
public class HeartbeatJob : IJob
{
public void Execute()
{
try
{
HeartbeatExporter.HeartBeat();
}
catch (Exception e)
{
Log.Error(e, "心跳上报异常");
}
}
}

View File

@ -0,0 +1,25 @@
using MasstransferCommon.Annotation;
using MasstransferExporter.ImageExporter;
using Serilog;
namespace MasstransferExporter.Jobs;
/// <summary>
/// 图片导出
/// 每天凌晨1点30分开始执行
/// </summary>
[ScheduledJob("0 30 1 ? * *")]
public class ImageExportJob : IJob
{
public void Execute()
{
try
{
ImageService.ImageExporter();
}
catch (Exception e)
{
Log.Error(e, "图片导出");
}
}
}

View File

@ -0,0 +1,9 @@
namespace MasstransferExporter.Jobs;
/// <summary>
/// 定时任务接口
/// </summary>
public interface IJob
{
void Execute();
}

View File

@ -0,0 +1,24 @@
using MasstransferCommon.Annotation;
using MasstransferExporter.LogExporter;
using Serilog;
namespace MasstransferExporter.Jobs;
/// <summary>
/// 日志导出任务
/// </summary>
[ScheduledJob("0 20 4 ? * *")]
public class LogExportJob : IJob
{
public async void Execute()
{
try
{
await LogFileExporter.ExportLogFile();
}
catch (Exception e)
{
Log.Error(e, "日志导出任务执行异常");
}
}
}

View File

@ -0,0 +1,24 @@
using MasstransferCommon.Annotation;
using MasstransferExporter.LogExporter;
using Serilog;
namespace MasstransferExporter.Jobs;
/// <summary>
/// 每天凌晨3 点15分上传用户操作日志
/// </summary>
[ScheduledJob("0 15 3 ? * *")]
public class OperationLogExportJob : IJob
{
public async void Execute()
{
try
{
await OperationLogExporter.ExportOperationLog();
}
catch (Exception e)
{
Log.Error(e, "用户操作日志导出失败");
}
}
}

View File

@ -0,0 +1,24 @@
using MasstransferCommon.Annotation;
using MasstransferExporter.DataExporter;
using Serilog;
namespace MasstransferExporter.Jobs;
/// <summary>
/// 动打作业定时任务
/// </summary>
[ScheduledJob("0 0/5 * * * ?")]
public class StrikeRecordJob : IJob
{
public async void Execute()
{
try
{
await StrikeRecordService.ReportStrikeRecord();
}
catch (Exception e)
{
Log.Error(e, "动打作业定时任务异常");
}
}
}

View File

@ -0,0 +1,24 @@
using MasstransferCommon.Annotation;
using MasstransferExporter.DataExporter;
using Serilog;
namespace MasstransferExporter.Jobs;
/// <summary>
/// 每天凌晨3点30分上传系统参数
/// </summary>
[ScheduledJob("0 30 3 ? * *")]
public class SystemParamJob : IJob
{
public async void Execute()
{
try
{
await SystemParamsService.ExportSystemParams();
}
catch (Exception e)
{
Log.Error(e, "系统参数上报定时任务执行异常");
}
}
}

View File

@ -0,0 +1,24 @@
using MasstransferCommon.Annotation;
using MasstransferExporter.StatExporter;
using Serilog;
namespace MasstransferExporter.Jobs;
/// <summary>
/// 每30秒导出一次系统状态
/// </summary>
[ScheduledJob("0/30 * * * * ? *")]
public class SystemStatExportJob : IJob
{
public async void Execute()
{
try
{
await SystemStatExporter.ExportSystemStat();
}
catch (Exception e)
{
Log.Error(e, "系统状态导出异常");
}
}
}

View File

@ -1,15 +1,10 @@
using MasstransferCommon.Config;
using MasstransferCommon.Events;
using MasstransferCommon.Scheduler;
using MasstransferCommunicate.Mqtt.Client;
using MasstransferCommunicate.Process.Client;
using MasstransferExporter.DataExporter;
using MasstransferExporter.Init;
using MasstransferExporter.StatExporter;
using MasstransferInfrastructure.Database.Sqlite;
using MasstransferInfrastructure.Mqtt.Model;
using Serilog;
using MqttParams = MasstransferCommon.Model.Entity.MqttParams;
namespace MasstransferExporter;
@ -19,39 +14,35 @@ class Program
public static async Task Main()
{
Log.Logger = LogConfiguration.GetLogger();
// 进行初始化调用
InstantUtil.Init();
var mqttParams = Db.Query<MqttParams>("select * from mqtt_params").FirstOrDefault();
// 启动mqtt连接
await MessageQueueHelper.InitConnect(new MqttConnectOptions()
try
{
ServerAddress = mqttParams.ServerAddress,
Port = mqttParams.Port,
UserName = mqttParams.UserName,
Password = mqttParams.Password
});
Log.Logger = LogConfiguration.GetLogger();
Thread.Sleep(3000);
// 进行初始化调用
InstantUtil.Init();
// 启动完成后,广播启动通知
EventBus<bool>.Publish(EventType.StartUp, true);
// 启动与主程序的通信
ProcessHelper.Init();
DelayScheduler.Delay(async () => await SystemParamsService.ExportSystemParams(),
TimeSpan.FromSeconds(3));
Thread.Sleep(3000);
// 启动完成后,广播启动通知
EventBus<bool>.Publish(EventType.StartUp, true);
// 启动与主程序的通信
// ProcessHelper.Init();
// 启动定时任务
await QuartzScheduler.Instance.StartAsync();
// SystemStatExporter.Collect();
Console.WriteLine("按任意键退出");
Console.ReadKey();
Console.WriteLine("按任意键退出");
Console.ReadKey();
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
// 停止定时任务
await QuartzScheduler.Instance.StopAsync();
}
}
}

View File

@ -1,21 +1,13 @@
using MasstransferCommon.Model.Constant;
using MasstransferCommon.Scheduler;
using MasstransferCommunicate.Mqtt.Client;
namespace MasstransferExporter.Stat;
namespace MasstransferExporter.StatExporter;
/// <summary>
/// 心跳
/// </summary>
public class HeartbeatExporter
{
/// <summary>
/// 启动心跳线程
/// </summary>
public static void StartHeartBeat()
{
JobScheduler.AddTask("HeartbeatExporter#StartHeartBeat", HeartBeat, 10000);
}
public static async void HeartBeat()
{

View File

@ -110,7 +110,7 @@ public class SqliteHelper
/// </summary>
/// <param name="id">数据ID</param>
/// <typeparam name="T">数据类型</typeparam>
public T GetById<T>(object id) where T : new()
public T? GetById<T>(object id) where T : new()
{
return _db.Get<T>(id);
}

View File

@ -1,5 +1,4 @@
using System.Reflection;
using MasstransferCommon.Model.Entity;
using MasstransferCommon.Model.Entity;
using MasstransferCommon.Utils;
using MasstransferCommunicate.Mqtt.Model;
using MasstransferInfrastructure.Database.Sqlite;
@ -8,7 +7,6 @@ using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Protocol;
using Serilog;
using MqttClient = MasstransferCommunicate.Mqtt.Client.MqttClient;
namespace MasstransferCommunicate.Mqtt.Client;
@ -20,15 +18,26 @@ public class MessageQueueHelper
private static readonly MqttClient Client = new();
private static readonly SqliteHelper Db = SqliteHelper.GetInstance();
/// <summary>
/// 初始化连接
/// </summary>
/// <param name="options"></param>
public static async Task<bool> InitConnect(MqttConnectOptions options)
public static async Task<bool> InitConnect()
{
try
{
var mqttParams = Db.Query<MqttParams>("select * from mqtt_params").FirstOrDefault();
// 启动mqtt连接
var options = new MqttConnectOptions()
{
ServerAddress = mqttParams!.ServerAddress,
Port = mqttParams.Port,
UserName = mqttParams.UserName,
Password = mqttParams.Password
};
if (!await Client.ConnectAsync(options)) return false;
// 连接成功后监听消息
Client.MessageReceived += HandleMessageReceived;