angle_dtu/http_server.go
2025-09-08 19:16:07 +08:00

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