From 9604c62f4c7561786921ace042a1797c82f7736c Mon Sep 17 00:00:00 2001 From: yarnom Date: Wed, 24 Sep 2025 19:06:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E9=80=9A=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E9=9B=B7=E8=BE=BE=E7=AB=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/database/models.go | 34 +- internal/database/stations_coords.go | 38 ++ internal/radar/scheduler.go | 24 ++ internal/server/gin.go | 11 + pkg/types/types.go | 22 +- templates/_header.html | 1 + templates/imdroid_radar.html | 561 +++++++++++++++++++++++++++ 7 files changed, 669 insertions(+), 22 deletions(-) create mode 100644 internal/database/stations_coords.go create mode 100644 templates/imdroid_radar.html diff --git a/internal/database/models.go b/internal/database/models.go index 564b421..92304c9 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -25,19 +25,23 @@ func GetOnlineDevicesCount(db *sql.DB) int { // GetStations 获取所有WH65LP站点列表 func GetStations(db *sql.DB) ([]types.Station, error) { 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` + SELECT DISTINCT s.station_id, + COALESCE(s.station_alias, '') as station_alias, + 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, + COALESCE(s.z, 0) as z, + COALESCE(s.y, 0) as y, + COALESCE(s.x, 0) as x + 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.station_alias, s.password, s.latitude, s.longitude, s.name, s.location, s.z, s.y, s.x + ORDER BY s.station_id` rows, err := db.Query(query) if err != nil { @@ -51,6 +55,7 @@ func GetStations(db *sql.DB) ([]types.Station, error) { var lastUpdate time.Time err := rows.Scan( &station.StationID, + &station.StationAlias, &station.StationName, &station.DeviceType, &lastUpdate, @@ -58,6 +63,9 @@ func GetStations(db *sql.DB) ([]types.Station, error) { &station.Longitude, &station.Name, &station.Location, + &station.Z, + &station.Y, + &station.X, ) if err != nil { continue diff --git a/internal/database/stations_coords.go b/internal/database/stations_coords.go new file mode 100644 index 0000000..eae295e --- /dev/null +++ b/internal/database/stations_coords.go @@ -0,0 +1,38 @@ +package database + +import ( + "context" + "database/sql" +) + +// StationCoord holds a station_id with geographic coordinates. +type StationCoord struct { + StationID string + Lat float64 + Lon float64 +} + +// ListWH65LPStationsWithLatLon returns WH65LP stations that have non-null and non-zero lat/lon. +func ListWH65LPStationsWithLatLon(ctx context.Context, db *sql.DB) ([]StationCoord, error) { + const q = ` + SELECT station_id, latitude, longitude + FROM stations + WHERE device_type = 'WH65LP' + AND latitude IS NOT NULL AND longitude IS NOT NULL + AND latitude <> 0 AND longitude <> 0 + ORDER BY station_id` + rows, err := db.QueryContext(ctx, q) + if err != nil { + return nil, err + } + defer rows.Close() + var out []StationCoord + for rows.Next() { + var s StationCoord + if err := rows.Scan(&s.StationID, &s.Lat, &s.Lon); err != nil { + continue + } + out = append(out, s) + } + return out, nil +} diff --git a/internal/radar/scheduler.go b/internal/radar/scheduler.go index 92147a6..373c5e5 100644 --- a/internal/radar/scheduler.go +++ b/internal/radar/scheduler.go @@ -349,6 +349,30 @@ func runOnceFromNMC(ctx context.Context, opts Options) error { if err := fetchAndStoreRadarRealtimeFor(ctx, "番禺雷达站", 23.022500, 113.331300); err != nil { log.Printf("[radar] realtime(PANYU) failed: %v", err) } + + // 并对 stations 表中符合条件(WH65LP 且有非零经纬度)的站点,抓取彩云实况并写入 radar_weather,alias=station_id + // 这样 radar_weather 可同时承载“雷达站别名”和“具体设备 station_id”两类记录。 + func() { + // 预先检查 token,避免对每个站点重复报错 + token := os.Getenv("CAIYUN_TOKEN") + if token == "" { + token = config.GetConfig().Forecast.CaiyunToken + } + if token == "" { + log.Printf("[radar] skip station realtime: missing CAIYUN_TOKEN") + return + } + coords, err := database.ListWH65LPStationsWithLatLon(ctx, database.GetDB()) + if err != nil { + log.Printf("[radar] list WH65LP stations failed: %v", err) + return + } + for _, s := range coords { + if err := fetchAndStoreRadarRealtimeFor(ctx, s.StationID, s.Lat, s.Lon); err != nil { + log.Printf("[radar] realtime(station=%s) failed: %v", s.StationID, err) + } + } + }() return nil } diff --git a/internal/server/gin.go b/internal/server/gin.go index 5ba03f0..15378dd 100644 --- a/internal/server/gin.go +++ b/internal/server/gin.go @@ -33,6 +33,7 @@ func StartGinServer() error { r.GET("/radar/guangzhou", radarGuangzhouHandler) r.GET("/radar/panyu", radarPanyuHandler) r.GET("/radar/haizhu", radarHaizhuHandler) + r.GET("/radar/imdroid", imdroidRadarHandler) // API路由组 api := r.Group("/api") @@ -114,6 +115,16 @@ func radarPanyuHandler(c *gin.Context) { c.HTML(http.StatusOK, "radar_panyu.html", data) } +func imdroidRadarHandler(c *gin.Context) { + data := types.PageData{ + Title: "英卓雷达站", + ServerTime: time.Now().Format("2006-01-02 15:04:05"), + OnlineDevices: database.GetOnlineDevicesCount(database.GetDB()), + TiandituKey: "0c260b8a094a4e0bc507808812cefdac", + } + c.HTML(http.StatusOK, "imdroid_radar.html", data) +} + // systemStatusHandler 处理系统状态API请求 func systemStatusHandler(c *gin.Context) { status := types.SystemStatus{ diff --git a/pkg/types/types.go b/pkg/types/types.go index 536ac97..24dd181 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -2,15 +2,19 @@ package types // Station 站点信息 type Station struct { - StationID string `json:"station_id"` - StationName string `json:"station_name"` - DeviceType string `json:"device_type"` - LastUpdate string `json:"last_update"` - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` - Name string `json:"name"` - Location string `json:"location"` - DecimalID string `json:"decimal_id"` + StationID string `json:"station_id"` + StationAlias string `json:"station_alias"` + StationName string `json:"station_name"` + DeviceType string `json:"device_type"` + LastUpdate string `json:"last_update"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Name string `json:"name"` + Location string `json:"location"` + Z int `json:"z"` + Y int `json:"y"` + X int `json:"x"` + DecimalID string `json:"decimal_id"` } // WeatherPoint 气象数据点 diff --git a/templates/_header.html b/templates/_header.html index 0fa79cf..f34e1be 100644 --- a/templates/_header.html +++ b/templates/_header.html @@ -4,6 +4,7 @@

{{ .Title }}