using log4net;
|
using Microsoft.AspNetCore.SignalR;
|
using Quartz;
|
using Quartz.Impl;
|
using Quartz.Impl.Triggers;
|
using System;
|
using System.Collections.Generic;
|
using System.Data;
|
using System.Linq;
|
using System.Net.Http;
|
using System.Threading.Tasks;
|
using VueWebCoreApi.Extensions;
|
using VueWebCoreApi.SignalR;
|
using VueWebCoreApi.Tools;
|
using static VueWebCoreApi.Quartz.HttpManager;
|
|
namespace VueWebCoreApi.Quartz
|
{
|
/// <summary>
|
/// 定时任务执行类:Quartz到时间后,自动运行这个类的Execute方法
|
/// 作用:发送HTTP请求、记录任务日志、推送执行结果
|
/// </summary>
|
public class HttpResultfulJob : IJob
|
{
|
// 日志记录工具(log4net)
|
private readonly ILog _log = LogManager.GetLogger(Startup.repository.Name, typeof(ChatHub));
|
// SignalR 实时推送工具(给前端推送消息)
|
private readonly IHubContext<ChatHub, IChatClient> _hubContext;
|
// HttpClient 工厂:用于发送 HTTP 请求
|
private readonly IHttpClientFactory _httpClientFactory;
|
// 数据库仓储:用于记录任务运行日志、更新最后执行时间
|
private readonly QuartzRepository _quartzRepo;
|
|
/// <summary>
|
/// 构造函数:依赖注入(自动赋值所有需要的服务)
|
/// </summary>
|
public HttpResultfulJob(IServiceProvider serviceProvider,
|
IHttpClientFactory httpClientFactory,
|
IHubContext<ChatHub, IChatClient> hubContext,
|
QuartzRepository quartzRepo)
|
{
|
_httpClientFactory = httpClientFactory;
|
_hubContext = hubContext;
|
_quartzRepo = quartzRepo;
|
}
|
|
/// <summary>
|
/// 【核心方法】
|
/// Quartz 调度器根据 Cron 表达式,到时间后**自动调用**这个方法
|
/// 作用:执行具体的任务逻辑(发送HTTP请求)
|
/// </summary>
|
public async Task Execute(IJobExecutionContext context)
|
{
|
// 记录任务开始执行时间
|
var beginTime = DateTime.Now;
|
// 从 Quartz 上下文获取当前要执行的任务配置(名称、分组、URL、Cron等)
|
var taskOptions = context.GetTaskOptions();
|
// 存储 HTTP 请求返回的结果信息
|
string httpMessage = string.Empty;
|
HttpResult result = new HttpResult();
|
|
// ==========================================
|
// 第一步:校验任务是否存在
|
// ==========================================
|
if (taskOptions == null)
|
{
|
// 如果任务为空,记录错误日志到数据库
|
var trigger = context.Trigger;
|
await _quartzRepo.AddJobRunLogAsync(trigger.Key.Name, trigger.Key.Group, beginTime, DateTime.Now, "未找到作业或可能被移除", taskOptions.MessagePush, taskOptions.PushUserCode);
|
return;
|
}
|
// 系统日志输出:任务开始执行
|
_log.Info($"作业[{taskOptions.TaskName}]开始:{beginTime:yyyy-MM-dd HH:mm:ss}");
|
|
// ==========================================
|
// 第二步:校验任务是否配置了请求地址
|
// ==========================================
|
if (string.IsNullOrEmpty(taskOptions.ApiUrl) || taskOptions.ApiUrl == "/")
|
{
|
// 未配置API地址,记录日志
|
await _quartzRepo.AddJobRunLogAsync(taskOptions.TaskName, taskOptions.GroupName, beginTime, DateTime.Now, "未配置URL", taskOptions.MessagePush, taskOptions.PushUserCode);
|
return;
|
}
|
// ==========================================
|
// 第三步:发送 HTTP 请求(核心业务)
|
// ==========================================
|
try
|
{
|
// 存储请求头(身份验证信息)
|
var headers = new Dictionary<string, string>();
|
// 如果配置了 AuthKey 和 AuthValue,添加到请求头
|
if (!string.IsNullOrEmpty(taskOptions.AuthKey) && !string.IsNullOrEmpty(taskOptions.AuthValue))
|
{
|
headers.Add(taskOptions.AuthKey.Trim(), taskOptions.AuthValue.Trim());
|
}
|
// 判断请求方式:GET 或 POST(默认POST)
|
var method = taskOptions.RequestType?.ToLower() == "get" ? HttpMethod.Get : HttpMethod.Post;
|
// 调用工具类,发送 HTTP 请求,并获取返回结果
|
result = await _httpClientFactory.HttpSendAsync(method, taskOptions.ApiUrl, taskOptions.RequestParameters, headers);
|
}
|
catch (Exception ex)
|
{
|
// 如果请求异常(网络错误、接口报错),记录异常信息
|
result.ErrorMsg = ex.Message;
|
_log.Error($"作业[{taskOptions.TaskName}]执行异常:{ex.Message}", ex);
|
}
|
|
try
|
{
|
// 1. 把本次执行记录写入 SQL Server 数据库(任务运行日志表)
|
await _quartzRepo.AddJobRunLogAsync(taskOptions.TaskName, taskOptions.GroupName, beginTime, DateTime.Now,result.StatusCode==200?result.Content.ToString():result.ErrorMsg, taskOptions.MessagePush, taskOptions.PushUserCode);
|
// 2. 更新任务表中的【最后执行时间】
|
await _quartzRepo.UpdateTaskLastRunTimeAsync(taskOptions.TaskName, taskOptions.GroupName, beginTime);
|
|
// ==========================================
|
// 以下是 SignalR 实时推送(给前端页面发消息)
|
// ==========================================
|
//判断请求是否成功
|
if (result.IsSuccess)
|
{
|
//写入任务子表
|
}
|
if (taskOptions.MessagePush == "N"||taskOptions.PushUserCode==null) //是否需要推送
|
{
|
_log.Info($"作业[{taskOptions.TaskName}]无需要推送的用户,跳过SignalR推送");
|
return;
|
}
|
//步骤2:提取用户编码(去重 + 空值过滤)
|
var departmentIDs = taskOptions.PushUserCode.Split(',').ToList();
|
|
if (departmentIDs.Count == 0)
|
{
|
_log.Info($"作业[{taskOptions.TaskName}]查询到的用户编码为空,跳过SignalR推送");
|
return;
|
}
|
// 步骤3:获取在线用户的连接ID(使用线程安全方法)
|
var targetConnIds = UserIdsStore.GetConnectionIdsByUserCodes(departmentIDs);
|
if (targetConnIds.Count == 0)
|
{
|
_log.Info($"作业[{taskOptions.TaskName}]目标用户均不在线,跳过SignalR推送");
|
return;
|
}
|
// 步骤4:构造推送消息(标准化格式)
|
var logContent = $"【{taskOptions.TaskName}】执行结果:{beginTime:yyyy-MM-dd HH:mm:ss} 至 {DateTime.Now:yyyy-MM-dd HH:mm:ss} | 结果:{(string.IsNullOrEmpty(httpMessage) ? "执行成功" : httpMessage)}";
|
// 步骤5:批量推送(增加超时+异常捕获)
|
_log.Info($"作业[{taskOptions.TaskName}]开始向[{targetConnIds.Count}]个在线连接推送消息:{logContent}");
|
// 设置推送超时时间(避免长时间阻塞)
|
var pushTask = _hubContext.Clients.Clients(targetConnIds).SendCustomUserMessage(logContent);
|
if (await Task.WhenAny(pushTask, Task.Delay(5000)) == pushTask)
|
{
|
await pushTask; // 推送成功
|
_log.Info($"作业[{taskOptions.TaskName}]SignalR推送完成,目标连接数:{targetConnIds.Count}");
|
}
|
else
|
{
|
_log.Warn($"作业[{taskOptions.TaskName}]SignalR推送超时(5秒),目标连接数:{targetConnIds.Count}");
|
}
|
}
|
catch (Exception ex)
|
{
|
// 记录日志写入或推送失败的异常
|
_log.Error($"作业[{taskOptions.TaskName}]日志写入/SignalR推送异常:{ex.Message}", ex);
|
}
|
// 系统日志输出:任务执行结束
|
_log.Info($"作业[{taskOptions.TaskName}]结束:{DateTime.Now:yyyy-MM-dd HH:mm:ss} 结果:{result.ErrorMsg}");
|
}
|
}
|
}
|