From 12ace5365027b405816ab16e1ba2fefb93ebf398 Mon Sep 17 00:00:00 2001 From: g82tt Date: Sun, 17 May 2026 10:41:59 +0800 Subject: [PATCH] =?UTF-8?q?V3=20=E5=9F=BA=E7=A1=80=E8=AE=BE=E6=96=BD:=20Ga?= =?UTF-8?q?tewayClient+3=E4=B8=AAQuartz=20Job+Startup=E6=B3=A8=E5=86=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api_sqlsugar/VolPro.WebApi/Startup copy.cs | 8 ++ .../Warehouse/Services/GatewayClient.cs | 73 +++++++++++++++++++ .../Warehouse/Services/HeartbeatMonitorJob.cs | 58 +++++++++++++++ .../Warehouse/Services/RealtimePollJob.cs | 20 +++++ .../Warehouse/Services/SyncDevicesJob.cs | 43 +++++++++++ doc/设计文档/Quartz_Job注册说明.txt | 19 +++++ 6 files changed, 221 insertions(+) create mode 100644 api_sqlsugar/Warehouse/Services/GatewayClient.cs create mode 100644 api_sqlsugar/Warehouse/Services/HeartbeatMonitorJob.cs create mode 100644 api_sqlsugar/Warehouse/Services/RealtimePollJob.cs create mode 100644 api_sqlsugar/Warehouse/Services/SyncDevicesJob.cs create mode 100644 doc/设计文档/Quartz_Job注册说明.txt diff --git a/api_sqlsugar/VolPro.WebApi/Startup copy.cs b/api_sqlsugar/VolPro.WebApi/Startup copy.cs index dafbf8e..f3867f3 100644 --- a/api_sqlsugar/VolPro.WebApi/Startup copy.cs +++ b/api_sqlsugar/VolPro.WebApi/Startup copy.cs @@ -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(); + services.AddMvc(options => { options.Filters.Add(typeof(ApiAuthorizeFilter)); diff --git a/api_sqlsugar/Warehouse/Services/GatewayClient.cs b/api_sqlsugar/Warehouse/Services/GatewayClient.cs new file mode 100644 index 0000000..7b9812b --- /dev/null +++ b/api_sqlsugar/Warehouse/Services/GatewayClient.cs @@ -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; + +/// +/// 网关 HTTP 客户端。封装 Vol.Pro 调用 IntegrationGateway B 组接口的逻辑。 +/// 所有对网关的请求统一经此类发出,便于连接池管理和错误处理。 +/// +public class GatewayClient +{ + private readonly IHttpClientFactory _httpFactory; + private readonly IConfiguration _config; + + public GatewayClient(IHttpClientFactory httpFactory, IConfiguration config) + { + _httpFactory = httpFactory; + _config = config; + } + + /// 创建带超时和默认头的 HttpClient + private HttpClient CreateClient() + { + var client = _httpFactory.CreateClient("VolPro"); + client.Timeout = TimeSpan.FromSeconds(30); + return client; + } + + /// + /// B3: 手动触发网关全量设备同步。 + /// POST {baseUrl}/api/gateway/devices/sync?adapter={adapterTypes} + /// + public async Task 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(); + } + + /// + /// B4: 获取设备实时点位值。 + /// GET {baseUrl}/api/gateway/realtime/{adapter}/{deviceId} + /// + public async Task 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(); + } + + /// + /// B5: 设备反向控制。 + /// POST {baseUrl}/api/gateway/realtime/{adapter}/control + /// + public async Task 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; + } +} diff --git a/api_sqlsugar/Warehouse/Services/HeartbeatMonitorJob.cs b/api_sqlsugar/Warehouse/Services/HeartbeatMonitorJob.cs new file mode 100644 index 0000000..a836fb9 --- /dev/null +++ b/api_sqlsugar/Warehouse/Services/HeartbeatMonitorJob.cs @@ -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; + +/// +/// 心跳超时检测任务。扫描心跳超时 30 秒的网关节点,标记为离线, +/// 并级联标记该节点下所有设备为离线。 +/// Cron 建议: 每 15 秒 ("0/15 * * * * ?") +/// +public class HeartbeatMonitorJob : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"]; + var gwSvc = sp.GetService(); + var devSvc = sp.GetService(); + 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} 台设备离线"); + } + } + } +} diff --git a/api_sqlsugar/Warehouse/Services/RealtimePollJob.cs b/api_sqlsugar/Warehouse/Services/RealtimePollJob.cs new file mode 100644 index 0000000..bd92a45 --- /dev/null +++ b/api_sqlsugar/Warehouse/Services/RealtimePollJob.cs @@ -0,0 +1,20 @@ +using Quartz; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; + +namespace VolPro.Warehouse.Services; + +/// +/// 实时数据轮询任务(Phase 2 完善)。 +/// 定时轮询 MC4.0 IoT 设备实时值 → 更新 iot_devicedata。 +/// Cron 建议: 每 10 秒 ("0/10 * * * * ?") +/// +public class RealtimePollJob : IJob +{ + public Task Execute(IJobExecutionContext context) + { + // TODO: Phase 2 — 遍历在线 MC4 网关,轮询实时值写入 iot_devicedata + return Task.CompletedTask; + } +} diff --git a/api_sqlsugar/Warehouse/Services/SyncDevicesJob.cs b/api_sqlsugar/Warehouse/Services/SyncDevicesJob.cs new file mode 100644 index 0000000..371d555 --- /dev/null +++ b/api_sqlsugar/Warehouse/Services/SyncDevicesJob.cs @@ -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; + +/// +/// 定时设备同步任务。遍历所有在线且启用的网关节点,触发全量设备同步。 +/// Cron 建议: 每 5 分钟 ("0 */5 * * * ?") +/// +public class SyncDevicesJob : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"]; + var gwSvc = sp.GetService(); + var client = sp.GetService(); + 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}"); + } + } + } +} diff --git a/doc/设计文档/Quartz_Job注册说明.txt b/doc/设计文档/Quartz_Job注册说明.txt new file mode 100644 index 0000000..93ec1fb --- /dev/null +++ b/doc/设计文档/Quartz_Job注册说明.txt @@ -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