温度看板搭建示例

This commit is contained in:
“hsc”
2026-08-28 10:20:14 +08:00
parent 03d46fb473
commit 3126c2e5ab
5 changed files with 195 additions and 7 deletions
@@ -1,14 +1,41 @@
using Microsoft.AspNetCore.Mvc;
using Service.Interface;
namespace WebAPI.Controllers
{
/// <summary>
/// 设备看板
/// 设备看板(温湿度)
/// </summary>
[ApiController]
[Route("api/inspection/dashboard")]
public class DeviceDashboardController : ControllerBase
{
// TODO: 实现 设备看板 相关接口
private readonly IDeviceDashboardService _dashboardService;
public DeviceDashboardController(IDeviceDashboardService dashboardService)
{
_dashboardService = dashboardService;
}
/// <summary>
/// 获取所有环境箱的最新温湿度数据(看板卡片,前端轮询调用)
/// </summary>
[HttpGet("realtime")]
public async Task<IActionResult> GetRealtime()
{
return Ok(await _dashboardService.GetRealtimeAsync());
}
/// <summary>
/// 获取指定环境箱最近 N 分钟的温湿度曲线数据
/// </summary>
/// <param name="deviceCode">设备标识,如 BOX-001</param>
/// <param name="minutes">最近多少分钟,默认 5,最大 60</param>
[HttpGet("curve")]
public async Task<IActionResult> GetCurve([FromQuery] string deviceCode, [FromQuery] int minutes = 5)
{
minutes = Math.Clamp(minutes, 1, 60);
return Ok(await _dashboardService.GetCurveAsync(deviceCode, minutes));
}
}
}
+7 -1
View File
@@ -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<TemperatureBoxSimulator>();
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();
@@ -0,0 +1,82 @@
using Model.Entity;
using ORM;
namespace WebAPI.Services
{
/// <summary>
/// 温度箱数据模拟器(临时用)
/// 每秒为 100 台环境箱生成一条模拟温湿度数据并批量写入数据库,
/// 真实设备接入后删除此类及 Program.cs 中的注册即可
/// </summary>
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<string, BoxState> _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<TemperatureBoxDataEntity>(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;
}
}
}
}
}