温度看板搭建示例
This commit is contained in:
@@ -1,14 +1,41 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Service.Interface;
|
||||||
|
|
||||||
namespace WebAPI.Controllers
|
namespace WebAPI.Controllers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设备看板
|
/// 设备看板(温湿度)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/inspection/dashboard")]
|
[Route("api/inspection/dashboard")]
|
||||||
public class DeviceDashboardController : ControllerBase
|
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
@@ -1,6 +1,7 @@
|
|||||||
using Common;
|
using Common;
|
||||||
using ORM;
|
using ORM;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using WebAPI.Services;
|
||||||
|
|
||||||
namespace WebAPI
|
namespace WebAPI
|
||||||
{
|
{
|
||||||
@@ -41,6 +42,9 @@ namespace WebAPI
|
|||||||
// 自动注册业务服务(Service.Interface -> Service.Implement)
|
// 自动注册业务服务(Service.Interface -> Service.Implement)
|
||||||
builder.Services.AddBusinessServices();
|
builder.Services.AddBusinessServices();
|
||||||
|
|
||||||
|
// 温度箱模拟数据:每秒生成 100 条温湿度入库(真实设备接入后删除)
|
||||||
|
builder.Services.AddHostedService<TemperatureBoxSimulator>();
|
||||||
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
builder.Services.AddSwaggerGen();
|
||||||
|
|
||||||
@@ -53,7 +57,9 @@ namespace WebAPI
|
|||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
// 关闭 Https 重定向:开启时会把 http 请求 307 到 https 端口,
|
||||||
|
// 导致前端 vite 代理被 CORS 拦截(Network Error);上位机/现场部署通常也只用 http
|
||||||
|
// app.UseHttpsRedirection();
|
||||||
|
|
||||||
app.UseAuthorization();
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,70 @@
|
|||||||
|
using Model;
|
||||||
|
using Model.Entity;
|
||||||
|
using ORM;
|
||||||
using Service.Interface;
|
using Service.Interface;
|
||||||
|
using SqlSugar;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Service.Implement
|
namespace Service.Implement
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设备看板 服务实现
|
/// 设备看板服务实现(温湿度)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DeviceDashboardService : IDeviceDashboardService
|
public class DeviceDashboardService : IDeviceDashboardService
|
||||||
{
|
{
|
||||||
// TODO: 实现 设备看板 相关方法
|
/// <summary>
|
||||||
|
/// 获取每台设备最新一条温湿度数据(看板卡片用)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<Result<List<TemperatureBoxDataEntity>>> GetRealtimeAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var db = SqlSugarContext.DbContext;
|
||||||
|
|
||||||
|
// 雪花 Id 单调递增,每台设备取最大 Id 即最新一条
|
||||||
|
var latestIds = await db.Queryable<TemperatureBoxDataEntity>()
|
||||||
|
.Where(x => x.IsDel == 0)
|
||||||
|
.GroupBy(x => x.DeviceCode)
|
||||||
|
.Select(x => SqlFunc.AggregateMax(x.Id))
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var list = await db.Queryable<TemperatureBoxDataEntity>()
|
||||||
|
.Where(x => latestIds.Contains(x.Id))
|
||||||
|
.OrderBy(x => x.DeviceCode)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Result<List<TemperatureBoxDataEntity>>.Success(list);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<List<TemperatureBoxDataEntity>>.Error("查询设备实时数据失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定设备最近 N 分钟的温湿度曲线数据(按时间升序)
|
||||||
|
/// </summary>
|
||||||
|
public async Task<Result<List<TemperatureBoxDataEntity>>> GetCurveAsync(string deviceCode, int minutes)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(deviceCode))
|
||||||
|
return Result<List<TemperatureBoxDataEntity>>.Error("设备标识不能为空");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var start = DateTime.Now.AddMinutes(-minutes);
|
||||||
|
var list = await SqlSugarContext.DbContext.Queryable<TemperatureBoxDataEntity>()
|
||||||
|
.Where(x => x.DeviceCode == deviceCode && x.IsDel == 0 && x.CreateTime >= start)
|
||||||
|
.OrderBy(x => x.CreateTime)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
return Result<List<TemperatureBoxDataEntity>>.Success(list);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Result<List<TemperatureBoxDataEntity>>.Error("查询温湿度曲线失败", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,25 @@
|
|||||||
|
using Model;
|
||||||
|
using Model.Entity;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Service.Interface
|
namespace Service.Interface
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设备看板 服务接口
|
/// 设备看板服务接口(温湿度)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IDeviceDashboardService
|
public interface IDeviceDashboardService
|
||||||
{
|
{
|
||||||
// TODO: 定义 设备看板 相关方法
|
/// <summary>
|
||||||
|
/// 获取每台设备最新一条温湿度数据(看板卡片用)
|
||||||
|
/// </summary>
|
||||||
|
Task<Result<List<TemperatureBoxDataEntity>>> GetRealtimeAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取指定设备最近 N 分钟的温湿度曲线数据(升序)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="deviceCode">设备标识</param>
|
||||||
|
/// <param name="minutes">最近多少分钟</param>
|
||||||
|
Task<Result<List<TemperatureBoxDataEntity>>> GetCurveAsync(string deviceCode, int minutes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user