299 lines
9.0 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 server
import (
"fmt"
"log"
"net/http"
"os"
"st
"strconv"
"os"
"weatherstation/internal/config"
"weatherstation/internal/database"
"weatherstation/pkg/types"
"github.com/gin-gonic/gin"
)
// StartGinServer 启动Gin Web服务器
func StartGinServer() error {
// 设置Gin模式
gin.SetMode(gin.ReleaseMode)
// 创建Gin引擎
r := gin.Default()
// 加载HTML模板
r.LoadHTMLGlob("templates/*")
// 静态文件服务
r.Static("/static", "./static")
// 前端SPAAngular静态资源与路由回退
// 构建产物目录:./core/frontend/dist/ui
r.GET("/ui/*filepath", func(c *gin.Context) {
// 物理文件优先,否则回退到 index.html支持前端路由
requested := c.Param("filepath")
if requested == "" || requested == "/" {
c.File("./core/frontend/dist/ui/index.html")
return
}
full := ".//core/frontend/dist/ui" + requested
if _, err := os.Stat(full); err == nil {
c.File(full)
return
}
c.File("./core/frontend/dist/ui/index.html")
})
// 路由设置
r.GET("/", indexHandler)
r.GET("/radar/nanning", radarNanningHandler)
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")
{
api.GET("/system/status", systemStatusHandler)
api.GET("/stations", getStationsHandler)
api.GET("/data", getDataHandler)
api.GET("/forecast", getForecastHandler)
api.GET("/radar/latest", latestRadarTileHandler)
api.GET("/radar/at", radarTileAtHandler)
api.GET("/radar/nearest", nearestRadarTileHandler)
api.GET("/radar/times", radarTileTimesHandler)
api.GET("/radar/weather_latest", latestRadarWeatherHandler)
api.GET("/radar/weather_at", radarWeatherAtHandler)
api.GET("/radar/weather_aliases", radarWeatherAliasesHandler)
api.GET("/radar/aliases", radarConfigAliasesHandler)
api.GET("/radar/weather_nearest", radarWeatherNearestHandler)
// Rain CMPA hourly tiles
api.GET("/rain/latest", latestRainTileHandler)
api.GET("/rain/at", rainTileAtHandler)
api.GET("/rain/nearest", nearestRainTileHandler)
api.GET("/rain/times", rainTileTimesHandler)
}
// 获取配置的Web端口
port := config.GetConfig().Server.WebPort
if port == 0 {
port = 10003 // 默认端口
}
// 启动服务器
fmt.Printf("Gin Web服务器启动监听端口 %d...\n", port)
return r.Run(fmt.Sprintf(":%d", port))
}
// indexHandler 处理主页请求
func indexHandler(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, "index.html", data)
}
// radarNanningHandler 南宁雷达站占位页
func radarNanningHandler(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)
}
// radarGuangzhouHandler 广州雷达站占位页
func radarGuangzhouHandler(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)
}
// radarHaizhuHandler 海珠雷达站占位页
func radarHaizhuHandler(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)
}
// radarPanyuHandler 番禺雷达站占位页
func radarPanyuHandler(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)
}
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{
OnlineDevices: database.GetOnlineDevicesCount(database.GetDB()),
ServerTime: time.Now().Format("2006-01-02 15:04:05"),
}
c.JSON(http.StatusOK, status)
}
// getStationsHandler 处理获取站点列表API请求
func getStationsHandler(c *gin.Context) {
stations, err := database.GetStations(database.GetDB())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询站点失败"})
return
}
// 为每个站点计算十进制ID
for i := range stations {
if len(stations[i].StationID) > 6 {
hexID := stations[i].StationID[len(stations[i].StationID)-6:]
if decimalID, err := strconv.ParseInt(hexID, 16, 64); err == nil {
stations[i].DecimalID = strconv.FormatInt(decimalID, 10)
}
}
}
c.JSON(http.StatusOK, stations)
}
// getDataHandler 处理获取历史数据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)
// 解析时间按本地CST解析避免被当作UTC
loc, _ := time.LoadLocation("Asia/Shanghai")
if loc == nil {
loc = time.FixedZone("CST", 8*3600)
}
start, err := time.ParseInLocation("2006-01-02 15:04:05", startTime, loc)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的开始时间"})
return
}
end, err := time.ParseInLocation("2006-01-02 15:04:05", endTime, loc)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的结束时间"})
return
}
// 获取数据改为基于10分钟聚合表的再聚合
var points []types.WeatherPoint
if interval == "raw" {
points, err = database.GetSeriesRaw(database.GetDB(), stationID, start, end)
} else {
points, err = database.GetSeriesFrom10Min(database.GetDB(), stationID, start, end, interval)
}
if err != nil {
log.Printf("查询数据失败: %v", err) // 记录具体错误到服务端日志
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("查询数据失败: %v", err),
})
return
}
c.JSON(http.StatusOK, points)
}
// getForecastHandler 处理获取预报数据API请求
func getForecastHandler(c *gin.Context) {
// 获取查询参数
stationID := c.Query("station_id")
startTime := c.Query("from")
endTime := c.Query("to")
provider := c.Query("provider")
versionsStr := c.DefaultQuery("versions", "1")
versions, _ := strconv.Atoi(versionsStr)
if versions <= 0 {
versions = 1
}
if stationID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少station_id参数"})
return
}
// 如果没有提供时间范围则默认查询未来3小时
loc, _ := time.LoadLocation("Asia/Shanghai")
if loc == nil {
loc = time.FixedZone("CST", 8*3600)
}
var start, end time.Time
var err error
if startTime == "" || endTime == "" {
// 默认查询未来3小时
now := time.Now().In(loc)
start = now.Truncate(time.Hour).Add(1 * time.Hour) // 下一个整点开始
end = start.Add(3 * time.Hour) // 未来3小时
} else {
// 解析用户提供的时间
start, err = time.ParseInLocation("2006-01-02 15:04:05", startTime, loc)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的开始时间格式"})
return
}
end, err = time.ParseInLocation("2006-01-02 15:04:05", endTime, loc)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的结束时间格式"})
return
}
}
// 获取预报数据
log.Printf("查询预报数据: stationID=%s, provider=%s, versions=%d, start=%s, end=%s", stationID, provider, versions, start.Format("2006-01-02 15:04:05"), end.Format("2006-01-02 15:04:05"))
points, err := database.GetForecastData(database.GetDB(), stationID, start, end, provider, versions)
if err != nil {
log.Printf("查询预报数据失败: %v", err)
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("查询预报数据失败: %v", err),
})
return
}
log.Printf("查询到预报数据: %d 条", len(points))
c.JSON(http.StatusOK, points)
}