From 1c88bde080dc787ec68024e5813d9e6d5781bfb5 Mon Sep 17 00:00:00 2001 From: yarnom Date: Fri, 8 Aug 2025 10:07:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=9C=B0=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gin_server.go | 209 +++++++++------ templates/index.html | 590 +++++++++++++++++++++++++++++++++---------- types.go | 14 +- 3 files changed, 598 insertions(+), 215 deletions(-) diff --git a/gin_server.go b/gin_server.go index 3bf6462..61b378e 100644 --- a/gin_server.go +++ b/gin_server.go @@ -4,6 +4,7 @@ import ( "database/sql" "fmt" "net/http" + "strconv" "time" "weatherstation/config" @@ -60,6 +61,7 @@ func indexHandler(c *gin.Context) { Title: "英卓气象站", ServerTime: time.Now().Format("2006-01-02 15:04:05"), OnlineDevices: getOnlineDevicesCount(), + TiandituKey: "0c260b8a094a4e0bc507808812cefdac", } c.HTML(http.StatusOK, "index.html", data) } @@ -79,11 +81,15 @@ func getStationsHandler(c *gin.Context) { SELECT DISTINCT s.station_id, COALESCE(s.password, '') as station_name, 'WH65LP' as device_type, - COALESCE(MAX(r.timestamp), '1970-01-01'::timestamp) as last_update + COALESCE(MAX(r.timestamp), '1970-01-01'::timestamp) as last_update, + COALESCE(s.latitude, 0) as latitude, + COALESCE(s.longitude, 0) as longitude, + COALESCE(s.name, '') as name, + COALESCE(s.location, '') as location FROM stations s LEFT JOIN rs485_weather_data r ON s.station_id = r.station_id WHERE s.station_id LIKE 'RS485-%' - GROUP BY s.station_id, s.password + GROUP BY s.station_id, s.password, s.latitude, s.longitude, s.name, s.location ORDER BY s.station_id` rows, err := ginDB.Query(query) @@ -97,98 +103,135 @@ func getStationsHandler(c *gin.Context) { for rows.Next() { var station Station var lastUpdate time.Time - err := rows.Scan(&station.StationID, &station.StationName, &station.DeviceType, &lastUpdate) + err := rows.Scan( + &station.StationID, + &station.StationName, + &station.DeviceType, + &lastUpdate, + &station.Latitude, + &station.Longitude, + &station.Name, + &station.Location, + ) if err != nil { continue } station.LastUpdate = lastUpdate.Format("2006-01-02 15:04:05") + + // 从station_id中提取十六进制ID并转换为十进制 + if len(station.StationID) > 6 { + hexID := station.StationID[len(station.StationID)-6:] + if decimalID, err := strconv.ParseInt(hexID, 16, 64); err == nil { + station.DecimalID = strconv.FormatInt(decimalID, 10) + } + } + stations = append(stations, station) } c.JSON(http.StatusOK, stations) } -// 获取站点历史数据API +// 获取历史数据API func getDataHandler(c *gin.Context) { - stationID := c.Query("station_id") + // 获取查询参数 + decimalID := c.Query("decimal_id") startTime := c.Query("start_time") endTime := c.Query("end_time") - interval := c.DefaultQuery("interval", "1hour") + interval := c.Query("interval") - if stationID == "" || startTime == "" || endTime == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "缺少必要参数"}) + // 将十进制ID转换为十六进制(补足6位) + decimalNum, err := strconv.ParseInt(decimalID, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效的站点编号"}) return } + hexID := fmt.Sprintf("%06X", decimalNum) + stationID := fmt.Sprintf("RS485-%s", hexID) - var intervalSQL string + // 构建查询SQL + var query string switch interval { case "10min": - intervalSQL = "10 minutes" + query = ` + WITH grouped_data AS ( + SELECT + date_trunc('hour', timestamp) + + (floor(date_part('minute', timestamp) / 10) * interval '10 minute') as time_group, + AVG(temperature) as temperature, + AVG(humidity) as humidity, + AVG(pressure) as pressure, + AVG(wind_speed) as wind_speed, + AVG(wind_direction) as wind_direction, + MAX(rainfall) - MIN(rainfall) as rainfall, + AVG(light) as light, + AVG(uv) as uv + FROM rs485_weather_data + WHERE station_id = $1 AND timestamp BETWEEN $2 AND $3 + GROUP BY time_group + ORDER BY time_group + ) + SELECT + to_char(time_group, 'YYYY-MM-DD HH24:MI:SS') as date_time, + temperature, humidity, pressure, wind_speed, wind_direction, + CASE WHEN rainfall < 0 THEN 0 ELSE rainfall END as rainfall, + light, uv + FROM grouped_data + ORDER BY time_group` case "30min": - intervalSQL = "30 minutes" - case "1hour": - intervalSQL = "1 hour" - default: - intervalSQL = "1 hour" + query = ` + WITH grouped_data AS ( + SELECT + date_trunc('hour', timestamp) + + (floor(date_part('minute', timestamp) / 30) * interval '30 minute') as time_group, + AVG(temperature) as temperature, + AVG(humidity) as humidity, + AVG(pressure) as pressure, + AVG(wind_speed) as wind_speed, + AVG(wind_direction) as wind_direction, + MAX(rainfall) - MIN(rainfall) as rainfall, + AVG(light) as light, + AVG(uv) as uv + FROM rs485_weather_data + WHERE station_id = $1 AND timestamp BETWEEN $2 AND $3 + GROUP BY time_group + ORDER BY time_group + ) + SELECT + to_char(time_group, 'YYYY-MM-DD HH24:MI:SS') as date_time, + temperature, humidity, pressure, wind_speed, wind_direction, + CASE WHEN rainfall < 0 THEN 0 ELSE rainfall END as rainfall, + light, uv + FROM grouped_data + ORDER BY time_group` + default: // 1hour + query = ` + WITH grouped_data AS ( + SELECT + date_trunc('hour', timestamp) as time_group, + AVG(temperature) as temperature, + AVG(humidity) as humidity, + AVG(pressure) as pressure, + AVG(wind_speed) as wind_speed, + AVG(wind_direction) as wind_direction, + MAX(rainfall) - MIN(rainfall) as rainfall, + AVG(light) as light, + AVG(uv) as uv + FROM rs485_weather_data + WHERE station_id = $1 AND timestamp BETWEEN $2 AND $3 + GROUP BY time_group + ORDER BY time_group + ) + SELECT + to_char(time_group, 'YYYY-MM-DD HH24:MI:SS') as date_time, + temperature, humidity, pressure, wind_speed, wind_direction, + CASE WHEN rainfall < 0 THEN 0 ELSE rainfall END as rainfall, + light, uv + FROM grouped_data + ORDER BY time_group` } - // 构建查询SQL - 使用时间窗口聚合 - query := fmt.Sprintf(` - WITH time_series AS ( - SELECT - date_trunc('hour', timestamp) + - INTERVAL '%s' * FLOOR(EXTRACT(EPOCH FROM timestamp - date_trunc('hour', timestamp)) / EXTRACT(EPOCH FROM INTERVAL '%s')) as time_bucket, - temperature, - humidity, - pressure, - wind_speed, - wind_direction, - rainfall, - light, - uv - FROM rs485_weather_data - WHERE station_id = $1 - AND timestamp >= $2::timestamp - AND timestamp <= $3::timestamp - ), - aggregated_data AS ( - SELECT - time_bucket, - ROUND(AVG(temperature)::numeric, 2) as temperature, - ROUND(AVG(humidity)::numeric, 2) as humidity, - ROUND(AVG(pressure)::numeric, 2) as pressure, - ROUND(AVG(wind_speed)::numeric, 2) as wind_speed, - -- 风向使用矢量平均 - ROUND(DEGREES(ATAN2( - AVG(SIN(RADIANS(wind_direction))), - AVG(COS(RADIANS(wind_direction))) - ))::numeric + CASE - WHEN DEGREES(ATAN2( - AVG(SIN(RADIANS(wind_direction))), - AVG(COS(RADIANS(wind_direction))) - )) < 0 THEN 360 - ELSE 0 - END, 2) as wind_direction, - -- 雨量使用差值计算 - ROUND((MAX(rainfall) - MIN(rainfall))::numeric, 3) as rainfall_diff, - ROUND(AVG(light)::numeric, 2) as light, - ROUND(AVG(uv)::numeric, 2) as uv - FROM time_series - GROUP BY time_bucket - ) - SELECT - time_bucket, - COALESCE(temperature, 0) as temperature, - COALESCE(humidity, 0) as humidity, - COALESCE(pressure, 0) as pressure, - COALESCE(wind_speed, 0) as wind_speed, - COALESCE(wind_direction, 0) as wind_direction, - COALESCE(rainfall_diff, 0) as rainfall, - COALESCE(light, 0) as light, - COALESCE(uv, 0) as uv - FROM aggregated_data - ORDER BY time_bucket`, intervalSQL, intervalSQL) - + // 执行查询 rows, err := ginDB.Query(query, stationID, startTime, endTime) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "查询数据失败"}) @@ -196,21 +239,27 @@ func getDataHandler(c *gin.Context) { } defer rows.Close() - var data []WeatherPoint + var weatherPoints []WeatherPoint for rows.Next() { var point WeatherPoint - var timestamp time.Time - err := rows.Scan(×tamp, &point.Temperature, &point.Humidity, - &point.Pressure, &point.WindSpeed, &point.WindDir, - &point.Rainfall, &point.Light, &point.UV) + err := rows.Scan( + &point.DateTime, + &point.Temperature, + &point.Humidity, + &point.Pressure, + &point.WindSpeed, + &point.WindDir, + &point.Rainfall, + &point.Light, + &point.UV, + ) if err != nil { continue } - point.DateTime = timestamp.Format("2006-01-02 15:04:05") - data = append(data, point) + weatherPoints = append(weatherPoints, point) } - c.JSON(http.StatusOK, data) + c.JSON(http.StatusOK, weatherPoints) } func StartGinServer() { diff --git a/templates/index.html b/templates/index.html index 7353f57..b79251d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -5,6 +5,9 @@ {{.Title}} + + + @@ -214,14 +275,22 @@ 系统信息: 服务器时间: {{.ServerTime}} | 在线设备: {{.OnlineDevices}} 个 - -
-

请选择监测站点:

-
正在加载站点信息...
-
-
+
+ + +
+ +
+ + +
+