package main import ( "encoding/json" "fmt" "html/template" "log" "net/http" "os" "path/filepath" "strconv" "strings" "time" ) // 启动HTTP服务器 func StartHTTPServer(address string) error { http.HandleFunc("/", handleIndex) // 静态资源 http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) http.HandleFunc("/api/data", handleGetData) http.HandleFunc("/api/latest", handleGetLatest) http.HandleFunc("/api/sensors", handleGetSensors) http.HandleFunc("/api/devices", handleGetDevices) fmt.Printf("HTTP服务器已启动,正在监听 %s\n", address) return http.ListenAndServe(address, nil) } // 处理主页 func handleIndex(w http.ResponseWriter, r *http.Request) { log.Printf("接收到主页请求: %s", r.URL.Path) templatePath := "templates/index.html" absPath, _ := filepath.Abs(templatePath) _, err := os.Stat(templatePath) if os.IsNotExist(err) { log.Printf("错误: 模板文件不存在: %s", absPath) http.Error(w, "模板文件不存在", http.StatusInternalServerError) return } tmpl, err := template.ParseFiles(templatePath) if err != nil { log.Printf("错误: 无法解析模板: %v", err) http.Error(w, "无法加载模板:"+err.Error(), http.StatusInternalServerError) return } log.Printf("模板加载成功,开始渲染") err = tmpl.Execute(w, nil) if err != nil { log.Printf("错误: 渲染模板出错: %v", err) http.Error(w, "渲染模板出错:"+err.Error(), http.StatusInternalServerError) } log.Printf("模板渲染完成") } // 处理获取传感器数据的API func handleGetData(w http.ResponseWriter, r *http.Request) { log.Printf("接收到获取数据请求: %s", r.URL.String()) w.Header().Set("Content-Type", "application/json") deviceID := r.URL.Query().Get("device_id") sensorIDStr := r.URL.Query().Get("sensor_id") limitStr := r.URL.Query().Get("limit") startDateStr := r.URL.Query().Get("start_date") endDateStr := r.URL.Query().Get("end_date") limit := 500 // 默认限制为500条数据 noLimit := false // 是否不限制数据条数 var sensorID int var err error if sensorIDStr != "" && sensorIDStr != "all" { sensorID, err = strconv.Atoi(sensorIDStr) if err != nil { log.Printf("错误: 无效的传感器ID: %s", sensorIDStr) http.Error(w, "无效的传感器ID", http.StatusBadRequest) return } } if limitStr != "" { if limitStr == "all" { noLimit = true limit = 0 // 设置为0表示不使用LIMIT子句 } else { limit, err = strconv.Atoi(limitStr) if err != nil || limit <= 0 { log.Printf("错误: 无效的记录数限制: %s", limitStr) http.Error(w, "无效的记录数限制", http.StatusBadRequest) return } } } // 使用北京时间解析前端传来的本地时间 var startDate, endDate time.Time shLoc, _ := time.LoadLocation("Asia/Shanghai") if startDateStr != "" { startDate, err = time.ParseInLocation("2006-01-02T15:04", startDateStr, shLoc) if err != nil { log.Printf("错误: 无效的开始日期: %s, %v", startDateStr, err) http.Error(w, "无效的开始日期", http.StatusBadRequest) return } } if endDateStr != "" { endDate, err = time.ParseInLocation("2006-01-02T15:04", endDateStr, shLoc) if err != nil { log.Printf("错误: 无效的结束日期: %s, %v", endDateStr, err) http.Error(w, "无效的结束日期", http.StatusBadRequest) return } } // 业务要求:必须提供 device_id;在设备下可选指定探头 if strings.TrimSpace(deviceID) == "" { http.Error(w, "必须提供 device_id", http.StatusBadRequest) return } var data []SensorData if sensorIDStr == "all" || sensorIDStr == "" { data, err = GetSensorDataByDevice(deviceID, limit, startDate, endDate) log.Printf("查询设备%s下所有传感器数据,limit=%d, 结果数量=%d", deviceID, limit, len(data)) } else { data, err = GetSensorDataByDeviceAndSensor(deviceID, sensorID, limit, startDate, endDate) log.Printf("查询设备%s下传感器%d数据,limit=%d, 结果数量=%d", deviceID, sensorID, limit, len(data)) } if err != nil { log.Printf("错误: 获取数据失败: %v", err) http.Error(w, "获取数据失败:"+err.Error(), http.StatusInternalServerError) return } // 输出最新数据的详细信息(当limit=1时) if limit == 1 && len(data) > 0 { latest := data[0] log.Printf("返回最新数据: ID=%d, SensorID=%d, X=%.3f, Y=%.3f, Z=%.3f, Temperature=%.1f, Timestamp=%s", latest.ID, latest.SensorID, latest.X, latest.Y, latest.Z, latest.Temperature, latest.Timestamp.Format("2006-01-02 15:04:05")) } if noLimit { log.Printf("成功获取到所有数据记录(%d条)", len(data)) } else { log.Printf("成功获取到 %d 条数据记录(限制:%d条)", len(data), limit) } if err := json.NewEncoder(w).Encode(data); err != nil { log.Printf("错误: 编码JSON失败: %v", err) http.Error(w, "编码JSON失败:"+err.Error(), http.StatusInternalServerError) } } // 处理获取所有传感器ID的API func handleGetSensors(w http.ResponseWriter, r *http.Request) { log.Printf("接收到获取传感器列表请求") w.Header().Set("Content-Type", "application/json") deviceID := r.URL.Query().Get("device_id") var ( sensorIDs []int err error ) if strings.TrimSpace(deviceID) != "" { sensorIDs, err = GetSensorIDsByDevice(deviceID) } else { sensorIDs, err = GetAllSensorIDs() } if err != nil { log.Printf("错误: 获取传感器ID失败: %v", err) http.Error(w, "获取传感器ID失败:"+err.Error(), http.StatusInternalServerError) return } log.Printf("成功获取到 %d 个传感器ID", len(sensorIDs)) if err := json.NewEncoder(w).Encode(sensorIDs); err != nil { log.Printf("错误: 编码JSON失败: %v", err) http.Error(w, "编码JSON失败:"+err.Error(), http.StatusInternalServerError) } } // 已移除 /api/clients,在线统计改由 /api/devices 提供(3小时窗口) // 处理获取设备列表及统计的API func handleGetDevices(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") list, err := GetDevicesWithStats() if err != nil { log.Printf("错误: 获取设备列表失败: %v", err) http.Error(w, "获取设备列表失败:"+err.Error(), http.StatusInternalServerError) return } // 构造响应,计算是否在线(3小时内有上报) type deviceResp struct { ID int `json:"id"` DeviceID string `json:"device_id"` SensorCount int `json:"sensor_count"` LastSeen *time.Time `json:"last_seen"` Online bool `json:"online"` } shLoc, _ := time.LoadLocation("Asia/Shanghai") resp := make([]deviceResp, 0, len(list)) now := time.Now().In(shLoc) for _, d := range list { var lsPtr *time.Time online := false if d.LastSeen.Valid { ls := d.LastSeen.Time.In(shLoc) lsPtr = &ls if now.Sub(ls) <= 3*time.Hour { online = true } } resp = append(resp, deviceResp{ ID: d.ID, DeviceID: d.DeviceID, SensorCount: d.SensorCount, LastSeen: lsPtr, Online: online, }) } if err := json.NewEncoder(w).Encode(resp); err != nil { log.Printf("错误: 编码设备列表JSON失败: %v", err) http.Error(w, "编码JSON失败:"+err.Error(), http.StatusInternalServerError) } } // 处理获取最新传感器数据的API func handleGetLatest(w http.ResponseWriter, r *http.Request) { log.Printf("接收到获取最新数据请求") w.Header().Set("Content-Type", "application/json") // 获取最新的一条数据 data, err := GetAllSensorData(1, time.Time{}, time.Time{}) if err != nil { log.Printf("错误: 获取最新数据失败: %v", err) http.Error(w, "获取最新数据失败:"+err.Error(), http.StatusInternalServerError) return } log.Printf("查询最新数据结果数量: %d", len(data)) if len(data) == 0 { log.Printf("数据库中没有任何数据") // 返回空数组而不是错误 if err := json.NewEncoder(w).Encode([]SensorData{}); err != nil { log.Printf("错误: 编码空数据JSON失败: %v", err) http.Error(w, "编码JSON失败:"+err.Error(), http.StatusInternalServerError) } return } // 输出最新数据的详细信息 latest := data[0] log.Printf("返回最新数据: ID=%d, SensorID=%d, X=%.3f, Y=%.3f, Z=%.3f, Temperature=%.1f, Timestamp=%s", latest.ID, latest.SensorID, latest.X, latest.Y, latest.Z, latest.Temperature, latest.Timestamp.Format("2006-01-02 15:04:05")) if err := json.NewEncoder(w).Encode(data); err != nil { log.Printf("错误: 编码最新数据JSON失败: %v", err) http.Error(w, "编码JSON失败:"+err.Error(), http.StatusInternalServerError) } }