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);
}
}