weather-station/gin_server.go
2025-08-21 15:41:04 +08:00

357 lines
9.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"database/sql"
"fmt"
"net/http"
"strconv"
"time"
"weatherstation/config"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
)
var ginDB *sql.DB
func initGinDB() error {
cfg := config.GetConfig()
connStr := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
cfg.Database.Host, cfg.Database.Port, cfg.Database.User,
cfg.Database.Password, cfg.Database.DBName, cfg.Database.SSLMode)
var err error
ginDB, err = sql.Open("postgres", connStr)
if err != nil {
return fmt.Errorf("无法连接到数据库: %v", err)
}
err = ginDB.Ping()
if err != nil {
return fmt.Errorf("数据库连接测试失败: %v", err)
}
return nil
}
// 获取在线设备数量
func getOnlineDevicesCount() int {
if ginDB == nil {
return 0
}
query := `
SELECT COUNT(DISTINCT station_id)
FROM rs485_weather_data
WHERE timestamp > NOW() - INTERVAL '5 minutes'`
var count int
err := ginDB.QueryRow(query).Scan(&count)
if err != nil {
return 0
}
return count
}
// 主页面处理器
func indexHandler(c *gin.Context) {
data := PageData{
Title: "英卓气象站",
ServerTime: time.Now().Format("2006-01-02 15:04:05"),
OnlineDevices: getOnlineDevicesCount(),
TiandituKey: "0c260b8a094a4e0bc507808812cefdac",
}
c.HTML(http.StatusOK, "index.html", data)
}
// 系统状态API
func systemStatusHandler(c *gin.Context) {
status := SystemStatus{
OnlineDevices: getOnlineDevicesCount(),
ServerTime: time.Now().Format("2006-01-02 15:04:05"),
}
c.JSON(http.StatusOK, status)
}
// 获取WH65LP站点列表API
func getStationsHandler(c *gin.Context) {
query := `
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(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, s.latitude, s.longitude, s.name, s.location
ORDER BY s.station_id`
rows, err := ginDB.Query(query)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询站点失败"})
return
}
defer rows.Close()
var stations []Station
for rows.Next() {
var station Station
var lastUpdate time.Time
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
func getDataHandler(c *gin.Context) {
// 获取查询参数
decimalID := c.Query("decimal_id")
startTime := c.Query("start_time")
endTime := c.Query("end_time")
interval := c.Query("interval")
// 将十进制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)
// 构建查询SQL统一风向矢量平均雨量为累计量的正增量求和
var query string
switch interval {
case "10min":
query = `
WITH base AS (
SELECT
date_trunc('hour', timestamp) + (floor(date_part('minute', timestamp) / 10) * interval '10 minute') as time_group,
timestamp as ts,
temperature, humidity, pressure, wind_speed, wind_direction, rainfall, light, uv
FROM rs485_weather_data
WHERE station_id = $1 AND timestamp BETWEEN $2 AND $3
),
rain_inc AS (
SELECT time_group, GREATEST(rainfall - LAG(rainfall) OVER (PARTITION BY time_group ORDER BY ts), 0) AS inc
FROM base
),
rain_sum AS (
SELECT time_group, SUM(inc) AS rainfall
FROM rain_inc
GROUP BY time_group
),
grouped_data AS (
SELECT
time_group,
AVG(temperature) as temperature,
AVG(humidity) as humidity,
AVG(pressure) as pressure,
AVG(wind_speed) as wind_speed,
DEGREES(ATAN2(AVG(SIN(RADIANS(wind_direction))), AVG(COS(RADIANS(wind_direction))))) AS wind_direction_raw,
AVG(light) as light,
AVG(uv) as uv
FROM base
GROUP BY time_group
)
SELECT
to_char(g.time_group, 'YYYY-MM-DD HH24:MI:SS') as date_time,
ROUND(g.temperature::numeric, 2) as temperature,
ROUND(g.humidity::numeric, 2) as humidity,
ROUND(g.pressure::numeric, 2) as pressure,
ROUND(g.wind_speed::numeric, 2) as wind_speed,
ROUND((CASE WHEN g.wind_direction_raw < 0 THEN g.wind_direction_raw + 360 ELSE g.wind_direction_raw END)::numeric, 2) AS wind_direction,
ROUND(COALESCE(r.rainfall, 0)::numeric, 3) as rainfall,
ROUND(g.light::numeric, 2) as light,
ROUND(g.uv::numeric, 2) as uv
FROM grouped_data g
LEFT JOIN rain_sum r ON r.time_group = g.time_group
ORDER BY g.time_group`
case "30min":
query = `
WITH base AS (
SELECT
date_trunc('hour', timestamp) + (floor(date_part('minute', timestamp) / 30) * interval '30 minute') as time_group,
timestamp as ts,
temperature, humidity, pressure, wind_speed, wind_direction, rainfall, light, uv
FROM rs485_weather_data
WHERE station_id = $1 AND timestamp BETWEEN $2 AND $3
),
rain_inc AS (
SELECT time_group, GREATEST(rainfall - LAG(rainfall) OVER (PARTITION BY time_group ORDER BY ts), 0) AS inc
FROM base
),
rain_sum AS (
SELECT time_group, SUM(inc) AS rainfall
FROM rain_inc
GROUP BY time_group
),
grouped_data AS (
SELECT
time_group,
AVG(temperature) as temperature,
AVG(humidity) as humidity,
AVG(pressure) as pressure,
AVG(wind_speed) as wind_speed,
DEGREES(ATAN2(AVG(SIN(RADIANS(wind_direction))), AVG(COS(RADIANS(wind_direction))))) AS wind_direction_raw,
AVG(light) as light,
AVG(uv) as uv
FROM base
GROUP BY time_group
)
SELECT
to_char(g.time_group, 'YYYY-MM-DD HH24:MI:SS') as date_time,
ROUND(g.temperature::numeric, 2) as temperature,
ROUND(g.humidity::numeric, 2) as humidity,
ROUND(g.pressure::numeric, 2) as pressure,
ROUND(g.wind_speed::numeric, 2) as wind_speed,
ROUND((CASE WHEN g.wind_direction_raw < 0 THEN g.wind_direction_raw + 360 ELSE g.wind_direction_raw END)::numeric, 2) AS wind_direction,
ROUND(COALESCE(r.rainfall, 0)::numeric, 3) as rainfall,
ROUND(g.light::numeric, 2) as light,
ROUND(g.uv::numeric, 2) as uv
FROM grouped_data g
LEFT JOIN rain_sum r ON r.time_group = g.time_group
ORDER BY g.time_group`
default: // 1hour
query = `
WITH base AS (
SELECT
date_trunc('hour', timestamp) as time_group,
timestamp as ts,
temperature, humidity, pressure, wind_speed, wind_direction, rainfall, light, uv
FROM rs485_weather_data
WHERE station_id = $1 AND timestamp BETWEEN $2 AND $3
),
rain_inc AS (
SELECT time_group, GREATEST(rainfall - LAG(rainfall) OVER (PARTITION BY time_group ORDER BY ts), 0) AS inc
FROM base
),
rain_sum AS (
SELECT time_group, SUM(inc) AS rainfall
FROM rain_inc
GROUP BY time_group
),
grouped_data AS (
SELECT
time_group,
AVG(temperature) as temperature,
AVG(humidity) as humidity,
AVG(pressure) as pressure,
AVG(wind_speed) as wind_speed,
DEGREES(ATAN2(AVG(SIN(RADIANS(wind_direction))), AVG(COS(RADIANS(wind_direction))))) AS wind_direction_raw,
AVG(light) as light,
AVG(uv) as uv
FROM base
GROUP BY time_group
)
SELECT
to_char(g.time_group, 'YYYY-MM-DD HH24:MI:SS') as date_time,
ROUND(g.temperature::numeric, 2) as temperature,
ROUND(g.humidity::numeric, 2) as humidity,
ROUND(g.pressure::numeric, 2) as pressure,
ROUND(g.wind_speed::numeric, 2) as wind_speed,
ROUND((CASE WHEN g.wind_direction_raw < 0 THEN g.wind_direction_raw + 360 ELSE g.wind_direction_raw END)::numeric, 2) AS wind_direction,
ROUND(COALESCE(r.rainfall, 0)::numeric, 3) as rainfall,
ROUND(g.light::numeric, 2) as light,
ROUND(g.uv::numeric, 2) as uv
FROM grouped_data g
LEFT JOIN rain_sum r ON r.time_group = g.time_group
ORDER BY g.time_group`
}
// 执行查询
rows, err := ginDB.Query(query, stationID, startTime, endTime)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询数据失败"})
return
}
defer rows.Close()
var weatherPoints []WeatherPoint
for rows.Next() {
var point WeatherPoint
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
}
weatherPoints = append(weatherPoints, point)
}
c.JSON(http.StatusOK, weatherPoints)
}
func StartGinServer() {
err := initGinDB()
if err != nil {
fmt.Printf("初始化Gin数据库连接失败: %v\n", err)
return
}
// 设置Gin模式
gin.SetMode(gin.ReleaseMode)
// 创建Gin引擎
r := gin.Default()
// 加载HTML模板
r.LoadHTMLGlob("templates/*")
// 静态文件服务
r.Static("/static", "./static")
// 路由设置
r.GET("/", indexHandler)
// API路由组
api := r.Group("/api")
{
api.GET("/system/status", systemStatusHandler)
api.GET("/stations", getStationsHandler)
api.GET("/data", getDataHandler)
}
// 启动服务器
fmt.Println("Gin Web服务器启动监听端口 10003...")
r.Run(":10003")
}