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