299 lines
7.3 KiB
Go
299 lines
7.3 KiB
Go
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")
|
||
}
|