From 3126c2e5ab0f867f1866fcbc137a766b95000932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Chsc=E2=80=9D?= <“huangsucan@kaiyili-lab.com”> Date: Fri, 28 Aug 2026 10:20:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B8=A9=E5=BA=A6=E7=9C=8B=E6=9D=BF=E6=90=AD?= =?UTF-8?q?=E5=BB=BA=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Inspection/DeviceDashboardController.cs | 31 ++++++- IOT_API/Program.cs | 8 +- IOT_API/Services/TemperatureBoxSimulator.cs | 82 +++++++++++++++++++ .../Inspection/DeviceDashboardService.cs | 62 +++++++++++++- .../Inspection/IDeviceDashboardService.cs | 19 ++++- 5 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 IOT_API/Services/TemperatureBoxSimulator.cs diff --git a/IOT_API/Controllers/Inspection/DeviceDashboardController.cs b/IOT_API/Controllers/Inspection/DeviceDashboardController.cs index 6906564..cf30d4f 100644 --- a/IOT_API/Controllers/Inspection/DeviceDashboardController.cs +++ b/IOT_API/Controllers/Inspection/DeviceDashboardController.cs @@ -1,14 +1,41 @@ using Microsoft.AspNetCore.Mvc; +using Service.Interface; namespace WebAPI.Controllers { /// - /// 设备看板 + /// 设备看板(温湿度) /// [ApiController] [Route("api/inspection/dashboard")] public class DeviceDashboardController : ControllerBase { - // TODO: 实现 设备看板 相关接口 + private readonly IDeviceDashboardService _dashboardService; + + public DeviceDashboardController(IDeviceDashboardService dashboardService) + { + _dashboardService = dashboardService; + } + + /// + /// 获取所有环境箱的最新温湿度数据(看板卡片,前端轮询调用) + /// + [HttpGet("realtime")] + public async Task GetRealtime() + { + return Ok(await _dashboardService.GetRealtimeAsync()); + } + + /// + /// 获取指定环境箱最近 N 分钟的温湿度曲线数据 + /// + /// 设备标识,如 BOX-001 + /// 最近多少分钟,默认 5,最大 60 + [HttpGet("curve")] + public async Task GetCurve([FromQuery] string deviceCode, [FromQuery] int minutes = 5) + { + minutes = Math.Clamp(minutes, 1, 60); + return Ok(await _dashboardService.GetCurveAsync(deviceCode, minutes)); + } } } diff --git a/IOT_API/Program.cs b/IOT_API/Program.cs index 19797e5..f039c2f 100644 --- a/IOT_API/Program.cs +++ b/IOT_API/Program.cs @@ -1,6 +1,7 @@ using Common; using ORM; using System.Text.Json; +using WebAPI.Services; namespace WebAPI { @@ -41,6 +42,9 @@ namespace WebAPI // 自动注册业务服务(Service.Interface -> Service.Implement) builder.Services.AddBusinessServices(); + // 温度箱模拟数据:每秒生成 100 条温湿度入库(真实设备接入后删除) + builder.Services.AddHostedService(); + builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); @@ -53,7 +57,9 @@ namespace WebAPI app.UseSwaggerUI(); } - app.UseHttpsRedirection(); + // 关闭 Https 重定向:开启时会把 http 请求 307 到 https 端口, + // 导致前端 vite 代理被 CORS 拦截(Network Error);上位机/现场部署通常也只用 http + // app.UseHttpsRedirection(); app.UseAuthorization(); diff --git a/IOT_API/Services/TemperatureBoxSimulator.cs b/IOT_API/Services/TemperatureBoxSimulator.cs new file mode 100644 index 0000000..5d48bf5 --- /dev/null +++ b/IOT_API/Services/TemperatureBoxSimulator.cs @@ -0,0 +1,82 @@ +using Model.Entity; +using ORM; + +namespace WebAPI.Services +{ + /// + /// 温度箱数据模拟器(临时用) + /// 每秒为 100 台环境箱生成一条模拟温湿度数据并批量写入数据库, + /// 真实设备接入后删除此类及 Program.cs 中的注册即可 + /// + public class TemperatureBoxSimulator : BackgroundService + { + private const int DeviceCount = 100; + private const double AlarmTemp = 80.0; // 高温报警阈值 + private static readonly Random Rnd = new(); + + // 每台设备维护一份模拟状态(随机游走需要基于上一次的值) + private class BoxState + { + public double Temperature = Rnd.Next(20, 60); + public double Humidity = Rnd.Next(30, 70); + } + + private readonly Dictionary _states = new(); + + public TemperatureBoxSimulator() + { + for (int i = 1; i <= DeviceCount; i++) + { + _states[$"BOX-{i:D3}"] = new BoxState(); + } + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + try + { + var now = DateTime.Now; + var batch = new List(DeviceCount); + + foreach (var kv in _states) + { + var s = kv.Value; + // 随机游走,让曲线平滑连续 + s.Temperature = Math.Clamp(s.Temperature + (Rnd.NextDouble() - 0.48) * 2, 15, 95); + s.Humidity = Math.Clamp(s.Humidity + (Rnd.NextDouble() - 0.5) * 3, 20, 90); + + bool alarm = s.Temperature >= AlarmTemp; + batch.Add(new TemperatureBoxDataEntity + { + DeviceCode = kv.Key, + DeviceType = "TemperatureBox", + DeviceTemperature = Math.Round(s.Temperature, 1), + DeviceHumidity = Math.Round(s.Humidity, 1), + Status = alarm ? "报警" : "运行", + AlarmInfo = alarm ? "温度超限报警" : "", + CreateTime = now + }); + } + + // 批量插入 + await SqlSugarContext.DbContext.Insertable(batch).ExecuteCommandAsync(); + } + catch (Exception ex) + { + Console.WriteLine($"[温度箱模拟器] 写入失败: {ex.Message}"); + } + + try + { + await Task.Delay(1000, stoppingToken); + } + catch (TaskCanceledException) + { + break; + } + } + } + } +} diff --git a/Service/Implement/Inspection/DeviceDashboardService.cs b/Service/Implement/Inspection/DeviceDashboardService.cs index 0082940..3d85167 100644 --- a/Service/Implement/Inspection/DeviceDashboardService.cs +++ b/Service/Implement/Inspection/DeviceDashboardService.cs @@ -1,12 +1,70 @@ +using Model; +using Model.Entity; +using ORM; using Service.Interface; +using SqlSugar; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; namespace Service.Implement { /// - /// 设备看板 服务实现 + /// 设备看板服务实现(温湿度) /// public class DeviceDashboardService : IDeviceDashboardService { - // TODO: 实现 设备看板 相关方法 + /// + /// 获取每台设备最新一条温湿度数据(看板卡片用) + /// + public async Task>> GetRealtimeAsync() + { + try + { + var db = SqlSugarContext.DbContext; + + // 雪花 Id 单调递增,每台设备取最大 Id 即最新一条 + var latestIds = await db.Queryable() + .Where(x => x.IsDel == 0) + .GroupBy(x => x.DeviceCode) + .Select(x => SqlFunc.AggregateMax(x.Id)) + .ToListAsync(); + + var list = await db.Queryable() + .Where(x => latestIds.Contains(x.Id)) + .OrderBy(x => x.DeviceCode) + .ToListAsync(); + + return Result>.Success(list); + } + catch (Exception ex) + { + return Result>.Error("查询设备实时数据失败", ex); + } + } + + /// + /// 获取指定设备最近 N 分钟的温湿度曲线数据(按时间升序) + /// + public async Task>> GetCurveAsync(string deviceCode, int minutes) + { + if (string.IsNullOrWhiteSpace(deviceCode)) + return Result>.Error("设备标识不能为空"); + + try + { + var start = DateTime.Now.AddMinutes(-minutes); + var list = await SqlSugarContext.DbContext.Queryable() + .Where(x => x.DeviceCode == deviceCode && x.IsDel == 0 && x.CreateTime >= start) + .OrderBy(x => x.CreateTime) + .ToListAsync(); + + return Result>.Success(list); + } + catch (Exception ex) + { + return Result>.Error("查询温湿度曲线失败", ex); + } + } } } diff --git a/Service/Interface/Inspection/IDeviceDashboardService.cs b/Service/Interface/Inspection/IDeviceDashboardService.cs index cb01c49..8952c98 100644 --- a/Service/Interface/Inspection/IDeviceDashboardService.cs +++ b/Service/Interface/Inspection/IDeviceDashboardService.cs @@ -1,10 +1,25 @@ +using Model; +using Model.Entity; +using System.Collections.Generic; +using System.Threading.Tasks; + namespace Service.Interface { /// - /// 设备看板 服务接口 + /// 设备看板服务接口(温湿度) /// public interface IDeviceDashboardService { - // TODO: 定义 设备看板 相关方法 + /// + /// 获取每台设备最新一条温湿度数据(看板卡片用) + /// + Task>> GetRealtimeAsync(); + + /// + /// 获取指定设备最近 N 分钟的温湿度曲线数据(升序) + /// + /// 设备标识 + /// 最近多少分钟 + Task>> GetCurveAsync(string deviceCode, int minutes); } }