V3 基础设施: GatewayClient+3个Quartz Job+Startup注册
This commit is contained in:
@@ -69,6 +69,14 @@ namespace VolPro.WebApi
|
||||
services.AddSession();
|
||||
services.AddMemoryCache();
|
||||
services.AddHttpContextAccessor();
|
||||
// ── 网关集成: HttpClient + GatewayClient 注册 ──
|
||||
services.AddHttpClient("VolPro", c =>
|
||||
{
|
||||
c.Timeout = TimeSpan.FromSeconds(30);
|
||||
c.DefaultRequestHeaders.Add("Accept", "application/json");
|
||||
});
|
||||
services.AddSingleton<VolPro.Warehouse.Services.GatewayClient>();
|
||||
|
||||
services.AddMvc(options =>
|
||||
{
|
||||
options.Filters.Add(typeof(ApiAuthorizeFilter));
|
||||
|
||||
73
api_sqlsugar/Warehouse/Services/GatewayClient.cs
Normal file
73
api_sqlsugar/Warehouse/Services/GatewayClient.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VolPro.Warehouse.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 网关 HTTP 客户端。封装 Vol.Pro 调用 IntegrationGateway B 组接口的逻辑。
|
||||
/// 所有对网关的请求统一经此类发出,便于连接池管理和错误处理。
|
||||
/// </summary>
|
||||
public class GatewayClient
|
||||
{
|
||||
private readonly IHttpClientFactory _httpFactory;
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
public GatewayClient(IHttpClientFactory httpFactory, IConfiguration config)
|
||||
{
|
||||
_httpFactory = httpFactory;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
/// <summary>创建带超时和默认头的 HttpClient</summary>
|
||||
private HttpClient CreateClient()
|
||||
{
|
||||
var client = _httpFactory.CreateClient("VolPro");
|
||||
client.Timeout = TimeSpan.FromSeconds(30);
|
||||
return client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// B3: 手动触发网关全量设备同步。
|
||||
/// POST {baseUrl}/api/gateway/devices/sync?adapter={adapterTypes}
|
||||
/// </summary>
|
||||
public async Task<JsonDocument?> TriggerFullSyncAsync(string baseUrl, string adapterTypes)
|
||||
{
|
||||
var http = CreateClient();
|
||||
var resp = await http.PostAsync(
|
||||
$"{baseUrl.TrimEnd('/')}/api/gateway/devices/sync?adapter={Uri.EscapeDataString(adapterTypes)}",
|
||||
null);
|
||||
if (!resp.IsSuccessStatusCode) return null;
|
||||
return await resp.Content.ReadFromJsonAsync<JsonDocument>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// B4: 获取设备实时点位值。
|
||||
/// GET {baseUrl}/api/gateway/realtime/{adapter}/{deviceId}
|
||||
/// </summary>
|
||||
public async Task<JsonDocument?> GetRealtimeAsync(string baseUrl, string adapter, string deviceId)
|
||||
{
|
||||
var http = CreateClient();
|
||||
var resp = await http.GetAsync(
|
||||
$"{baseUrl.TrimEnd('/')}/api/gateway/realtime/{Uri.EscapeDataString(adapter)}/{Uri.EscapeDataString(deviceId)}");
|
||||
if (!resp.IsSuccessStatusCode) return null;
|
||||
return await resp.Content.ReadFromJsonAsync<JsonDocument>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// B5: 设备反向控制。
|
||||
/// POST {baseUrl}/api/gateway/realtime/{adapter}/control
|
||||
/// </summary>
|
||||
public async Task<bool> ControlDeviceAsync(string baseUrl, string adapter, string deviceId, int pointIndex, double value)
|
||||
{
|
||||
var http = CreateClient();
|
||||
var resp = await http.PostAsJsonAsync(
|
||||
$"{baseUrl.TrimEnd('/')}/api/gateway/realtime/{adapter}/control",
|
||||
new { deviceId, pointIndex, value });
|
||||
return resp.IsSuccessStatusCode;
|
||||
}
|
||||
}
|
||||
58
api_sqlsugar/Warehouse/Services/HeartbeatMonitorJob.cs
Normal file
58
api_sqlsugar/Warehouse/Services/HeartbeatMonitorJob.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Quartz;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Warehouse.IServices;
|
||||
using VolPro.Entity.DomainModels;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace VolPro.Warehouse.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 心跳超时检测任务。扫描心跳超时 30 秒的网关节点,标记为离线,
|
||||
/// 并级联标记该节点下所有设备为离线。
|
||||
/// Cron 建议: 每 15 秒 ("0/15 * * * * ?")
|
||||
/// </summary>
|
||||
public class HeartbeatMonitorJob : IJob
|
||||
{
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"];
|
||||
var gwSvc = sp.GetService<Igateway_nodesService>();
|
||||
var devSvc = sp.GetService<Ibase_deviceService>();
|
||||
if (gwSvc == null) return;
|
||||
|
||||
var timeout = DateTime.Now.AddSeconds(-30);
|
||||
|
||||
// 扫描心跳超时的网关(当前在线但心跳超时)
|
||||
var offlineNodes = await gwSvc.FindAsIQueryable(
|
||||
x => x.IsOnline == "在线" && x.LastHeartbeat < timeout)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var node in offlineNodes)
|
||||
{
|
||||
// 标记网关离线
|
||||
node.IsOnline = "离线";
|
||||
await gwSvc.FindAsIQueryable(x => x.NodeId == node.NodeId)
|
||||
.FirstAsync(); // 确保实体被跟踪
|
||||
// 直接通过 DbContext 更新
|
||||
var dbProp = gwSvc.GetType().BaseType?.GetProperty("DbContext");
|
||||
if (dbProp != null) continue; // fallback: 通过 FindAsIQueryable 重新获取更新
|
||||
|
||||
Console.WriteLine($"[HeartbeatMonitorJob] 网关 {node.NodeCode} 心跳超时,标记离线");
|
||||
|
||||
// 级联标记该网关下所有设备离线
|
||||
if (devSvc != null)
|
||||
{
|
||||
var devices = await devSvc.FindAsIQueryable(
|
||||
x => x.GatewayNodeId == node.NodeId && x.IsOnline == "在线")
|
||||
.ToListAsync();
|
||||
foreach (var dev in devices)
|
||||
{
|
||||
dev.IsOnline = "离线";
|
||||
}
|
||||
Console.WriteLine($"[HeartbeatMonitorJob] 级联 {devices.Count} 台设备离线");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
api_sqlsugar/Warehouse/Services/RealtimePollJob.cs
Normal file
20
api_sqlsugar/Warehouse/Services/RealtimePollJob.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Quartz;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VolPro.Warehouse.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 实时数据轮询任务(Phase 2 完善)。
|
||||
/// 定时轮询 MC4.0 IoT 设备实时值 → 更新 iot_devicedata。
|
||||
/// Cron 建议: 每 10 秒 ("0/10 * * * * ?")
|
||||
/// </summary>
|
||||
public class RealtimePollJob : IJob
|
||||
{
|
||||
public Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
// TODO: Phase 2 — 遍历在线 MC4 网关,轮询实时值写入 iot_devicedata
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
43
api_sqlsugar/Warehouse/Services/SyncDevicesJob.cs
Normal file
43
api_sqlsugar/Warehouse/Services/SyncDevicesJob.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using Quartz;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Warehouse.IServices;
|
||||
using VolPro.Entity.DomainModels;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace VolPro.Warehouse.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 定时设备同步任务。遍历所有在线且启用的网关节点,触发全量设备同步。
|
||||
/// Cron 建议: 每 5 分钟 ("0 */5 * * * ?")
|
||||
/// </summary>
|
||||
public class SyncDevicesJob : IJob
|
||||
{
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"];
|
||||
var gwSvc = sp.GetService<Igateway_nodesService>();
|
||||
var client = sp.GetService<GatewayClient>();
|
||||
if (gwSvc == null || client == null) return;
|
||||
|
||||
// 遍历所有在线且启用的网关
|
||||
var onlineNodes = await gwSvc.FindAsIQueryable(
|
||||
x => x.IsOnline == "在线" && x.Enable == "启用" && x.BaseUrl != null)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var node in onlineNodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 触发网关全量同步
|
||||
await client.TriggerFullSyncAsync(node.BaseUrl!, node.AdapterTypes ?? "");
|
||||
Console.WriteLine($"[SyncDevicesJob] 网关 {node.NodeCode} 同步触发成功");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"[SyncDevicesJob] 网关 {node.NodeCode} 同步失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
doc/设计文档/Quartz_Job注册说明.txt
Normal file
19
doc/设计文档/Quartz_Job注册说明.txt
Normal file
@@ -0,0 +1,19 @@
|
||||
Phase V3.6: Quartz Job 注册说明
|
||||
═══════════════════════════════
|
||||
|
||||
以下 3 个 Job 需在 Vol.Pro 管理端 → Quartz 任务管理 中手动创建:
|
||||
|
||||
1. SyncDevicesJob
|
||||
- 类名: VolPro.Warehouse.Services.SyncDevicesJob
|
||||
- Cron: 0 */5 * * * ? (每5分钟)
|
||||
- 说明: 遍历在线网关触发全量设备同步
|
||||
|
||||
2. HeartbeatMonitorJob
|
||||
- 类名: VolPro.Warehouse.Services.HeartbeatMonitorJob
|
||||
- Cron: 0/15 * * * * ? (每15秒)
|
||||
- 说明: 检测心跳超时标记离线
|
||||
|
||||
3. RealtimePollJob (Phase 2 启用)
|
||||
- 类名: VolPro.Warehouse.Services.RealtimePollJob
|
||||
- Cron: 0/10 * * * * ? (每10秒)
|
||||
- 说明: 轮询MC4.0实时值写iot_devicedata
|
||||
Reference in New Issue
Block a user