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
{
///
/// 定时任务执行类:Quartz到时间后,自动运行这个类的Execute方法
/// 作用:发送HTTP请求、记录任务日志、推送执行结果
///
public class HttpResultfulJob : IJob
{
// 日志记录工具(log4net)
private readonly ILog _log = LogManager.GetLogger(Startup.repository.Name, typeof(ChatHub));
// SignalR 实时推送工具(给前端推送消息)
private readonly IHubContext _hubContext;
// HttpClient 工厂:用于发送 HTTP 请求
private readonly IHttpClientFactory _httpClientFactory;
// 数据库仓储:用于记录任务运行日志、更新最后执行时间
private readonly QuartzRepository _quartzRepo;
///
/// 构造函数:依赖注入(自动赋值所有需要的服务)
///
public HttpResultfulJob(IServiceProvider serviceProvider,
IHttpClientFactory httpClientFactory,
IHubContext hubContext,
QuartzRepository quartzRepo)
{
_httpClientFactory = httpClientFactory;
_hubContext = hubContext;
_quartzRepo = quartzRepo;
}
///
/// 【核心方法】
/// Quartz 调度器根据 Cron 表达式,到时间后**自动调用**这个方法
/// 作用:执行具体的任务逻辑(发送HTTP请求)
///
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();
// 如果配置了 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}");
}
}
}