Fix-F2-F4: B4-batch+批量离线+凭据安全+前端地址+异常日志+滞后窗+console清理+API统一+Swagger+文档同步

This commit is contained in:
2026-06-03 17:39:26 +08:00
parent 6835ce86ce
commit 8413a52a28
10 changed files with 220 additions and 22 deletions

View File

@@ -3,8 +3,10 @@ using Microsoft.Extensions.DependencyInjection;
using Warehouse.IServices;
using VolPro.Entity.DomainModels;
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Warehouse.IRepositories;
namespace VolPro.Warehouse.Services;
@@ -12,46 +14,60 @@ namespace VolPro.Warehouse.Services;
/// 心跳超时检测任务。扫描心跳超时 30 秒的网关节点,标记为离线,
/// 并级联标记该节点下所有设备为离线。
/// Cron 建议: 每 15 秒 ("0/15 * * * * ?")
///
/// 设备与网关的关联通过 AdapterCode 前缀匹配(如设备 AdapterCode="MC4:31ku" 匹配网关 AdapterTypes="MC4:31ku")。
/// </summary>
public class HeartbeatMonitorJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
var sp = (IServiceProvider)context.JobDetail.JobDataMap["ServiceProvider"];
if (sp == null) return;
var gwSvc = sp.GetService<Igateway_nodesService>();
var devSvc = sp.GetService<Ibase_deviceService>();
if (gwSvc == null) return;
var gwRepo = sp.GetService<Igateway_nodesRepository>();
var devRepo = sp.GetService<Ibase_deviceRepository>();
if (gwSvc == null || gwRepo == null || devRepo == null) return;
var timeout = DateTime.Now.AddSeconds(-30);
// 扫描心跳超时的网关(当前在线但心跳超时)
var offlineNodes = await gwSvc.FindAsIQueryable(
x => x.IsOnline == "在线" && x.LastHeartbeat < timeout)
.ToListAsync();
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 重新获取更新
try { gwRepo.Update(node); } catch { }
Console.WriteLine($"[HeartbeatMonitorJob] 网关 {node.NodeCode} 心跳超时,标记离线");
// 级联标记该网关下所有设备离线
if (devSvc != null)
// 级联标记该网关下所有设备离线(批量 SQL
try
{
var devices = await devSvc.FindAsIQueryable(
x => x.NodeId == node.NodeId && x.IsOnline == "在线")
.ToListAsync();
foreach (var dev in devices)
var adapterPrefixes = (node.AdapterTypes ?? "")
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(t => t.Trim()).ToList();
if (adapterPrefixes.Any())
{
dev.IsOnline = "离线";
var allDevices = await devRepo.FindAsIQueryable(
d => d.IsOnline == "在线").ToListAsync();
var matched = allDevices
.Where(d => adapterPrefixes.Any(p => (d.AdapterCode ?? "").StartsWith(p)))
.ToList();
if (matched.Any())
{
foreach (var dev in matched) dev.IsOnline = "离线";
devRepo.UpdateRange(matched);
Console.WriteLine($"[HeartbeatMonitorJob] 级联 {matched.Count} 台设备离线");
}
}
Console.WriteLine($"[HeartbeatMonitorJob] 级联 {devices.Count} 台设备离线");
}
catch (Exception ex)
{
Console.Error.WriteLine($"[HeartbeatMonitorJob] 级联离线失败: {ex.Message}");
}
}
}