weather-station/gin_server.go
2025-08-08 10:07:50 +08:00

299 lines
7.3 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 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":
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`
}
// 执行查询
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")
}