fix:调整是数据显示与数据获取

This commit is contained in:
fengyarnom 2025-05-15 18:03:51 +08:00
parent 16615a5029
commit 4a6c6e58ea
4 changed files with 87 additions and 27 deletions

View File

@ -67,7 +67,9 @@ func (dao *SensorDAO) GetAggregatedData(start, end time.Time, interval string) (
ROUND(AVG(temperature)/10, 1) AS avg_temp, ROUND(AVG(temperature)/10, 1) AS avg_temp,
MAX(rainfall) - MIN(rainfall) AS rainfall, MAX(rainfall) - MIN(rainfall) AS rainfall,
ROUND(AVG(humidity)/10, 1) AS avg_humidity, ROUND(AVG(humidity)/10, 1) AS avg_humidity,
ROUND(AVG(wind_speed)/10, 1) AS avg_wind_speed ROUND(AVG(wind_speed)/10, 1) AS avg_wind_speed,
ROUND(AVG(atm_pressure)/10, 1) AS avg_atm_pressure,
ROUND(AVG(solar_radiation)/10, 1) AS avg_solar_radiation
FROM sensor_data FROM sensor_data
WHERE timestamp BETWEEN ? AND ? WHERE timestamp BETWEEN ? AND ?
GROUP BY bucket_time GROUP BY bucket_time
@ -92,7 +94,7 @@ func (dao *SensorDAO) GetAggregatedData(start, end time.Time, interval string) (
var data model.AggregatedData var data model.AggregatedData
var tsStr string var tsStr string
err := rows.Scan(&tsStr, &data.AvgTemperature, &data.Rainfall, err := rows.Scan(&tsStr, &data.AvgTemperature, &data.Rainfall,
&data.AvgHumidity, &data.AvgWindSpeed) &data.AvgHumidity, &data.AvgWindSpeed, &data.AvgAtmPressure, &data.AvgSolarRadiation)
if err != nil { if err != nil {
logger.Logger.Printf("扫描数据行失败: %v", err) logger.Logger.Printf("扫描数据行失败: %v", err)
continue continue

View File

@ -19,9 +19,11 @@ type SensorData struct {
// AggregatedData 聚合数据结构 // AggregatedData 聚合数据结构
type AggregatedData struct { type AggregatedData struct {
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
AvgTemperature float64 `json:"avg_temperature"` AvgTemperature float64 `json:"avg_temperature"`
Rainfall float64 `json:"rainfall"` Rainfall float64 `json:"rainfall"`
AvgHumidity float64 `json:"avg_humidity"` AvgHumidity float64 `json:"avg_humidity"`
AvgWindSpeed float64 `json:"avg_wind_speed"` AvgWindSpeed float64 `json:"avg_wind_speed"`
AvgAtmPressure float64 `json:"atm_pressure"` // 平均大气压
AvgSolarRadiation float64 `json:"solar_radiation"` // 平均太阳辐射
} }

View File

@ -68,7 +68,7 @@ func handleConnection(sensor *SensorComm) {
return return
} }
// 设置定时器,每分钟查询一次 // 设置定时器,每5分钟查询一次
ticker := time.NewTicker(time.Minute * 5) ticker := time.NewTicker(time.Minute * 5)
defer ticker.Stop() defer ticker.Stop()
@ -76,14 +76,28 @@ func handleConnection(sensor *SensorComm) {
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
done := make(chan bool) done := make(chan bool)
// 设置tcp保持活动状态防止连接断开
tcpConn, ok := sensor.conn.(*net.TCPConn)
if ok {
tcpConn.SetKeepAlive(true)
tcpConn.SetKeepAlivePeriod(30 * time.Second)
}
// 处理接收数据的goroutine // 处理接收数据的goroutine
go func() { go func() {
for { for {
// 设置读取超时 // 设置读取超时 - 增加超时时间到10分钟大于查询间隔
sensor.conn.SetReadDeadline(time.Now().Add(time.Second * 30)) sensor.conn.SetReadDeadline(time.Now().Add(10 * time.Minute))
n, err := sensor.conn.Read(buffer) n, err := sensor.conn.Read(buffer)
if err != nil { if err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
// 超时错误不一定意味着连接断开,继续等待
logger.Logger.Printf("读取超时: %v, 等待下一次查询", err)
continue
}
// 其他错误才认为连接断开
logger.Logger.Printf("读取数据失败: %v", err) logger.Logger.Printf("读取数据失败: %v", err)
done <- true done <- true
return return
@ -109,6 +123,7 @@ func handleConnection(sensor *SensorComm) {
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
logger.Logger.Printf("定时查询触发 [%s]", sensor.address)
if err := sensor.sendQuery(); err != nil { if err := sensor.sendQuery(); err != nil {
return return
} }
@ -174,7 +189,13 @@ func (s *SensorComm) Close() {
// 启动TCP服务器 // 启动TCP服务器
func StartTCPServer(dao *dao.SensorDAO) error { func StartTCPServer(dao *dao.SensorDAO) error {
listener, err := net.Listen("tcp", tcpPort) // 创建TCP监听器并设置TCP选项
addr, err := net.ResolveTCPAddr("tcp", tcpPort)
if err != nil {
return fmt.Errorf("解析TCP地址失败: %v", err)
}
listener, err := net.ListenTCP("tcp", addr)
if err != nil { if err != nil {
return fmt.Errorf("启动TCP服务器失败: %v", err) return fmt.Errorf("启动TCP服务器失败: %v", err)
} }
@ -186,12 +207,17 @@ func StartTCPServer(dao *dao.SensorDAO) error {
var mu sync.Mutex var mu sync.Mutex
for { for {
conn, err := listener.Accept() conn, err := listener.AcceptTCP()
if err != nil { if err != nil {
logger.Logger.Printf("接受连接失败: %v", err) logger.Logger.Printf("接受连接失败: %v", err)
continue continue
} }
// 设置TCP连接选项
conn.SetKeepAlive(true)
conn.SetKeepAlivePeriod(30 * time.Second)
conn.SetLinger(0) // 立即关闭
mu.Lock() mu.Lock()
// 关闭旧连接 // 关闭旧连接
if currentConn != nil { if currentConn != nil {

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>气象站数据监控</title> <title>雨量计数据</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style> <style>
body { body {
@ -16,7 +16,6 @@
padding: 10px; padding: 10px;
text-align: center; text-align: center;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
background-color: #f8f9fa;
} }
.container { .container {
@ -118,7 +117,7 @@
</head> </head>
<body> <body>
<div class="header"> <div class="header">
<h1>气象站数据监控</h1> <h1>雨量计数据</h1>
</div> </div>
<div class="container"> <div class="container">
@ -142,6 +141,11 @@
<input type="datetime-local" id="endDate"> <input type="datetime-local" id="endDate">
</div> </div>
<div class="control-group">
<label for="adaptiveScale">自适应范围:</label>
<input type="checkbox" id="adaptiveScale">
</div>
<div class="control-group"> <div class="control-group">
<button onclick="queryData()">查询</button> <button onclick="queryData()">查询</button>
<button onclick="exportData()">导出数据</button> <button onclick="exportData()">导出数据</button>
@ -161,6 +165,8 @@
<th>平均温度(℃)</th> <th>平均温度(℃)</th>
<th>平均湿度(%)</th> <th>平均湿度(%)</th>
<th>平均风速(m/s)</th> <th>平均风速(m/s)</th>
<th>大气压(hPa)</th>
<th>太阳辐射(W/m²)</th>
</tr> </tr>
</thead> </thead>
<tbody id="tableBody"></tbody> <tbody id="tableBody"></tbody>
@ -241,6 +247,26 @@
date.getMinutes().toString().padStart(2, '0'); date.getMinutes().toString().padStart(2, '0');
}); });
// 是否使用自适应范围
const useAdaptiveScale = document.getElementById('adaptiveScale').checked;
// 计算降雨量和温度的范围
let rainfallMin = 0;
let rainfallMax = 50;
let tempMin = -10;
let tempMax = 40;
if (useAdaptiveScale && data.length > 0) {
// 找出温度的最小值和最大值,并添加一些边距
const temps = data.map(item => item.avg_temperature);
tempMin = Math.floor(Math.min(...temps) - 5);
tempMax = Math.ceil(Math.max(...temps) + 5);
// 找出降雨量的最大值,并添加一些边距
const rainfalls = data.map(item => item.rainfall);
rainfallMax = Math.ceil(Math.max(...rainfalls) * 1.2) || 10; // 如果最大值是0则默认为10
}
mainChart = new Chart(ctx, { mainChart = new Chart(ctx, {
data: { data: {
labels: labels, labels: labels,
@ -282,10 +308,10 @@
grid: { grid: {
drawOnChartArea: false drawOnChartArea: false
}, },
// 设置降雨量的合理范围 // 设置降雨量的范围
min: 0, min: rainfallMin,
max: 50, // 根据实际情况可调整 max: rainfallMax,
suggestedMax: 10 suggestedMax: Math.min(10, rainfallMax)
}, },
'y-temp': { 'y-temp': {
type: 'linear', type: 'linear',
@ -294,11 +320,11 @@
display: true, display: true,
text: '温度(℃)' text: '温度(℃)'
}, },
// 设置温度的合理范围 // 设置温度的范围
min: -10, min: tempMin,
max: 40, max: tempMax,
suggestedMin: 0, suggestedMin: Math.max(0, tempMin),
suggestedMax: 30 suggestedMax: Math.min(30, tempMax)
} }
} }
} }
@ -331,6 +357,8 @@
<td>${item.avg_temperature.toFixed(1)}</td> <td>${item.avg_temperature.toFixed(1)}</td>
<td>${item.avg_humidity.toFixed(1)}</td> <td>${item.avg_humidity.toFixed(1)}</td>
<td>${item.avg_wind_speed.toFixed(1)}</td> <td>${item.avg_wind_speed.toFixed(1)}</td>
<td>${item.atm_pressure ? (item.atm_pressure/10).toFixed(1) : 'N/A'}</td>
<td>${item.solar_radiation ? (item.solar_radiation/10).toFixed(1) : 'N/A'}</td>
`; `;
tbody.appendChild(row); tbody.appendChild(row);
}); });
@ -340,7 +368,7 @@
function exportData() { function exportData() {
// 从表格中获取完整数据 // 从表格中获取完整数据
const tableRows = document.querySelectorAll('#tableBody tr'); const tableRows = document.querySelectorAll('#tableBody tr');
let csv = '时间,降雨量(mm),温度(℃),湿度(%),风速(m/s)\n'; let csv = '时间,降雨量(mm),温度(℃),湿度(%),风速(m/s),大气压(hPa),太阳辐射(W/m²)\n';
tableRows.forEach(row => { tableRows.forEach(row => {
const cells = row.querySelectorAll('td'); const cells = row.querySelectorAll('td');
@ -349,7 +377,9 @@
cells[1].textContent, // 降雨量 cells[1].textContent, // 降雨量
cells[2].textContent, // 温度 cells[2].textContent, // 温度
cells[3].textContent, // 湿度 cells[3].textContent, // 湿度
cells[4].textContent // 风速 cells[4].textContent, // 风速
cells[5].textContent, // 大气压
cells[6].textContent // 太阳辐射
]; ];
csv += rowData.join(',') + '\n'; csv += rowData.join(',') + '\n';
}); });
@ -357,7 +387,7 @@
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a'); const link = document.createElement('a');
link.href = URL.createObjectURL(blob); link.href = URL.createObjectURL(blob);
link.download = '气象站数据.csv'; link.download = '雨量计数据.csv';
link.click(); link.click();
} }