276 lines
8.5 KiB
Go
276 lines
8.5 KiB
Go
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)
|
||
}
|
||
}
|