feat: 优化页面
This commit is contained in:
parent
ed8048c111
commit
480c0f7404
147
clients.go
147
clients.go
@ -1,147 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 客户端信息结构
|
|
||||||
type ClientInfo struct {
|
|
||||||
IP string // IP地址
|
|
||||||
Port string // 端口
|
|
||||||
LastSeen time.Time // 最后活跃时间
|
|
||||||
IsConnected bool // 是否当前连接
|
|
||||||
}
|
|
||||||
|
|
||||||
// 客户端列表(使用互斥锁保护的映射)
|
|
||||||
var (
|
|
||||||
clientsMutex sync.Mutex
|
|
||||||
clients = make(map[string]*ClientInfo)
|
|
||||||
)
|
|
||||||
|
|
||||||
// addClient 添加客户端
|
|
||||||
func addClient(addr string) {
|
|
||||||
clientsMutex.Lock()
|
|
||||||
defer clientsMutex.Unlock()
|
|
||||||
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
Logger.Printf("解析客户端地址失败 %s: %v", addr, err)
|
|
||||||
host = addr
|
|
||||||
port = "unknown"
|
|
||||||
}
|
|
||||||
|
|
||||||
clients[addr] = &ClientInfo{
|
|
||||||
IP: host,
|
|
||||||
Port: port,
|
|
||||||
LastSeen: time.Now(),
|
|
||||||
IsConnected: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.Printf("添加新客户端: %s", addr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateClientLastSeen 更新客户端最后活跃时间
|
|
||||||
func updateClientLastSeen(addr string) {
|
|
||||||
clientsMutex.Lock()
|
|
||||||
defer clientsMutex.Unlock()
|
|
||||||
|
|
||||||
if client, exists := clients[addr]; exists {
|
|
||||||
client.LastSeen = time.Now()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeClient 移除客户端(标记断开)
|
|
||||||
func removeClient(addr string) {
|
|
||||||
clientsMutex.Lock()
|
|
||||||
defer clientsMutex.Unlock()
|
|
||||||
|
|
||||||
if client, exists := clients[addr]; exists {
|
|
||||||
client.IsConnected = false
|
|
||||||
Logger.Printf("客户端断开连接: %s", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAllClients 获取所有客户端信息
|
|
||||||
func getAllClients() []map[string]interface{} {
|
|
||||||
clientsMutex.Lock()
|
|
||||||
defer clientsMutex.Unlock()
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
result := make([]map[string]interface{}, 0, len(clients))
|
|
||||||
|
|
||||||
for addr, client := range clients {
|
|
||||||
lastSeenDuration := now.Sub(client.LastSeen)
|
|
||||||
|
|
||||||
// 清理24小时前的记录
|
|
||||||
if lastSeenDuration > 24*time.Hour {
|
|
||||||
delete(clients, addr)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// 连接状态判断:当前连接且2小时内活跃为在线
|
|
||||||
isOnline := client.IsConnected && lastSeenDuration < 2*time.Hour
|
|
||||||
var connectionStatus string
|
|
||||||
if isOnline {
|
|
||||||
connectionStatus = "保持连接"
|
|
||||||
} else if client.IsConnected {
|
|
||||||
connectionStatus = "连接超时"
|
|
||||||
} else {
|
|
||||||
connectionStatus = "已断开"
|
|
||||||
}
|
|
||||||
|
|
||||||
result = append(result, map[string]interface{}{
|
|
||||||
"address": addr,
|
|
||||||
"ip": client.IP,
|
|
||||||
"port": client.Port,
|
|
||||||
"lastSeen": client.LastSeen,
|
|
||||||
"isOnline": isOnline,
|
|
||||||
"connectionStatus": connectionStatus,
|
|
||||||
"lastSeenFormatted": formatDuration(lastSeenDuration),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatDuration 格式化持续时间为友好的字符串
|
|
||||||
func formatDuration(d time.Duration) string {
|
|
||||||
if d < time.Minute {
|
|
||||||
return "刚刚"
|
|
||||||
} else if d < time.Hour {
|
|
||||||
return fmt.Sprintf("%d分钟前", int(d.Minutes()))
|
|
||||||
} else if d < 24*time.Hour {
|
|
||||||
hours := int(d.Hours())
|
|
||||||
minutes := int(d.Minutes()) % 60
|
|
||||||
if minutes == 0 {
|
|
||||||
return fmt.Sprintf("%d小时前", hours)
|
|
||||||
} else {
|
|
||||||
return fmt.Sprintf("%d小时%d分钟前", hours, minutes)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Sprintf("%d天前", int(d.Hours()/24))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// startClientCleanup 启动清理过期客户端的goroutine
|
|
||||||
func startClientCleanup() {
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
time.Sleep(1 * time.Hour) // 每小时检查一次
|
|
||||||
|
|
||||||
clientsMutex.Lock()
|
|
||||||
now := time.Now()
|
|
||||||
|
|
||||||
for addr, client := range clients {
|
|
||||||
if now.Sub(client.LastSeen) > 24*time.Hour {
|
|
||||||
delete(clients, addr)
|
|
||||||
Logger.Printf("移除过期客户端: %s", addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clientsMutex.Unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
187
db.go
187
db.go
@ -22,7 +22,7 @@ func InitDB() error {
|
|||||||
dbName := "probe_db"
|
dbName := "probe_db"
|
||||||
|
|
||||||
// 连接字符串
|
// 连接字符串
|
||||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", username, password, host, port, dbName)
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&loc=Asia%%2FShanghai", username, password, host, port, dbName)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
db, err = sql.Open("mysql", dsn)
|
db, err = sql.Open("mysql", dsn)
|
||||||
@ -81,26 +81,22 @@ func SaveSensorData(sensorID int, x, y, z, temperature float64, deviceID string)
|
|||||||
|
|
||||||
// 获取传感器数据 - 添加时间范围,包含温度字段
|
// 获取传感器数据 - 添加时间范围,包含温度字段
|
||||||
func GetSensorData(sensorID int, limit int, startDate time.Time, endDate time.Time) ([]SensorData, error) {
|
func GetSensorData(sensorID int, limit int, startDate time.Time, endDate time.Time) ([]SensorData, error) {
|
||||||
query := `SELECT id, sensor_id, x_value, y_value, z_value,
|
query := "SELECT id, sensor_id, x_value, y_value, z_value, COALESCE(temperature, 0) as temperature, `timestamp` as timestamp FROM sensor_data WHERE sensor_id = ?"
|
||||||
COALESCE(temperature, 0) as temperature,
|
|
||||||
timestamp as timestamp
|
|
||||||
FROM sensor_data
|
|
||||||
WHERE sensor_id = ?`
|
|
||||||
|
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
args = append(args, sensorID)
|
args = append(args, sensorID)
|
||||||
|
|
||||||
if !startDate.IsZero() {
|
if !startDate.IsZero() {
|
||||||
query += " AND timestamp >= ?"
|
query += " AND `timestamp` >= ?"
|
||||||
args = append(args, startDate)
|
args = append(args, startDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !endDate.IsZero() {
|
if !endDate.IsZero() {
|
||||||
query += " AND timestamp <= ?"
|
query += " AND `timestamp` <= ?"
|
||||||
args = append(args, endDate)
|
args = append(args, endDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
query += " ORDER BY timestamp DESC"
|
query += " ORDER BY `timestamp` DESC"
|
||||||
|
|
||||||
// 只有当limit > 0时才添加LIMIT子句
|
// 只有当limit > 0时才添加LIMIT子句
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
@ -138,25 +134,21 @@ func GetSensorData(sensorID int, limit int, startDate time.Time, endDate time.Ti
|
|||||||
|
|
||||||
// 获取所有传感器数据,包含温度字段
|
// 获取所有传感器数据,包含温度字段
|
||||||
func GetAllSensorData(limit int, startDate time.Time, endDate time.Time) ([]SensorData, error) {
|
func GetAllSensorData(limit int, startDate time.Time, endDate time.Time) ([]SensorData, error) {
|
||||||
query := `SELECT id, sensor_id, x_value, y_value, z_value,
|
query := "SELECT id, sensor_id, x_value, y_value, z_value, COALESCE(temperature, 0) as temperature, `timestamp` as timestamp FROM sensor_data WHERE 1=1"
|
||||||
COALESCE(temperature, 0) as temperature,
|
|
||||||
timestamp as timestamp
|
|
||||||
FROM sensor_data
|
|
||||||
WHERE 1=1`
|
|
||||||
|
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
|
|
||||||
if !startDate.IsZero() {
|
if !startDate.IsZero() {
|
||||||
query += " AND timestamp >= ?"
|
query += " AND `timestamp` >= ?"
|
||||||
args = append(args, startDate)
|
args = append(args, startDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !endDate.IsZero() {
|
if !endDate.IsZero() {
|
||||||
query += " AND timestamp <= ?"
|
query += " AND `timestamp` <= ?"
|
||||||
args = append(args, endDate)
|
args = append(args, endDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
query += " ORDER BY timestamp DESC"
|
query += " ORDER BY `timestamp` DESC"
|
||||||
|
|
||||||
// 只有当limit > 0时才添加LIMIT子句
|
// 只有当limit > 0时才添加LIMIT子句
|
||||||
if limit > 0 {
|
if limit > 0 {
|
||||||
@ -235,6 +227,18 @@ type Device struct {
|
|||||||
RegCodeHex sql.NullString
|
RegCodeHex sql.NullString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeviceWithStats 包含设备统计信息
|
||||||
|
type DeviceWithStats struct {
|
||||||
|
ID int
|
||||||
|
DeviceID string
|
||||||
|
ForwardEnable bool
|
||||||
|
Host sql.NullString
|
||||||
|
Port sql.NullInt64
|
||||||
|
RegCodeHex sql.NullString
|
||||||
|
SensorCount int
|
||||||
|
LastSeen sql.NullTime
|
||||||
|
}
|
||||||
|
|
||||||
// GetDevice 获取设备配置(按设备字符串ID)
|
// GetDevice 获取设备配置(按设备字符串ID)
|
||||||
func GetDevice(deviceID string) (*Device, error) {
|
func GetDevice(deviceID string) (*Device, error) {
|
||||||
row := db.QueryRow(`SELECT id, device_id, COALESCE(forward_enable, 0) as forward_enable, host, port, reg_code_hex FROM devices WHERE device_id = ?`, deviceID)
|
row := db.QueryRow(`SELECT id, device_id, COALESCE(forward_enable, 0) as forward_enable, host, port, reg_code_hex FROM devices WHERE device_id = ?`, deviceID)
|
||||||
@ -249,3 +253,152 @@ func GetDevice(deviceID string) (*Device, error) {
|
|||||||
d.ForwardEnable = fe != 0
|
d.ForwardEnable = fe != 0
|
||||||
return &d, nil
|
return &d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDevicesWithStats 获取设备列表及统计
|
||||||
|
func GetDevicesWithStats() ([]DeviceWithStats, error) {
|
||||||
|
query := `SELECT d.id,
|
||||||
|
d.device_id,
|
||||||
|
COALESCE(d.forward_enable, 0) AS forward_enable,
|
||||||
|
d.host,
|
||||||
|
d.port,
|
||||||
|
d.reg_code_hex,
|
||||||
|
COALESCE(COUNT(DISTINCT sd.sensor_id), 0) AS sensor_count,
|
||||||
|
MAX(sd.timestamp) AS last_seen
|
||||||
|
FROM devices d
|
||||||
|
LEFT JOIN sensor_data sd ON sd.device_id = d.device_id
|
||||||
|
GROUP BY d.id, d.device_id, d.forward_enable, d.host, d.port, d.reg_code_hex
|
||||||
|
ORDER BY d.id`
|
||||||
|
|
||||||
|
rows, err := db.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var list []DeviceWithStats
|
||||||
|
for rows.Next() {
|
||||||
|
var item DeviceWithStats
|
||||||
|
var fe int
|
||||||
|
if err := rows.Scan(&item.ID, &item.DeviceID, &fe, &item.Host, &item.Port, &item.RegCodeHex, &item.SensorCount, &item.LastSeen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
item.ForwardEnable = fe != 0
|
||||||
|
list = append(list, item)
|
||||||
|
}
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSensorIDsByDevice 获取某设备下的传感器ID
|
||||||
|
func GetSensorIDsByDevice(deviceID string) ([]int, error) {
|
||||||
|
rows, err := db.Query(`SELECT DISTINCT sensor_id FROM sensor_data WHERE device_id = ? ORDER BY sensor_id`, deviceID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var ids []int
|
||||||
|
for rows.Next() {
|
||||||
|
var id int
|
||||||
|
if err := rows.Scan(&id); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSensorDataByDevice 获取某设备下的数据(可选时间范围)
|
||||||
|
func GetSensorDataByDevice(deviceID string, limit int, startDate, endDate time.Time) ([]SensorData, error) {
|
||||||
|
query := "SELECT id, sensor_id, x_value, y_value, z_value, " +
|
||||||
|
"COALESCE(temperature, 0) as temperature, " +
|
||||||
|
"`timestamp` as timestamp " +
|
||||||
|
"FROM sensor_data " +
|
||||||
|
"WHERE device_id = ?"
|
||||||
|
|
||||||
|
var args []interface{}
|
||||||
|
args = append(args, deviceID)
|
||||||
|
|
||||||
|
if !startDate.IsZero() {
|
||||||
|
query += " AND `timestamp` >= ?"
|
||||||
|
args = append(args, startDate)
|
||||||
|
}
|
||||||
|
if !endDate.IsZero() {
|
||||||
|
query += " AND `timestamp` <= ?"
|
||||||
|
args = append(args, endDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
query += " ORDER BY `timestamp` DESC"
|
||||||
|
if limit > 0 {
|
||||||
|
query += " LIMIT ?"
|
||||||
|
args = append(args, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var result []SensorData
|
||||||
|
for rows.Next() {
|
||||||
|
var data SensorData
|
||||||
|
var xInt, yInt, zInt, tempInt int
|
||||||
|
if err := rows.Scan(&data.ID, &data.SensorID, &xInt, &yInt, &zInt, &tempInt, &data.Timestamp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data.X = float64(xInt) / SCALING_FACTOR
|
||||||
|
data.Y = float64(yInt) / SCALING_FACTOR
|
||||||
|
data.Z = float64(zInt) / SCALING_FACTOR
|
||||||
|
data.Temperature = float64(tempInt) / SCALING_FACTOR
|
||||||
|
result = append(result, data)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSensorDataByDeviceAndSensor 获取某设备某传感器的数据
|
||||||
|
func GetSensorDataByDeviceAndSensor(deviceID string, sensorID int, limit int, startDate, endDate time.Time) ([]SensorData, error) {
|
||||||
|
query := "SELECT id, sensor_id, x_value, y_value, z_value, " +
|
||||||
|
"COALESCE(temperature, 0) as temperature, " +
|
||||||
|
"`timestamp` as timestamp " +
|
||||||
|
"FROM sensor_data " +
|
||||||
|
"WHERE device_id = ? AND sensor_id = ?"
|
||||||
|
|
||||||
|
var args []interface{}
|
||||||
|
args = append(args, deviceID, sensorID)
|
||||||
|
|
||||||
|
if !startDate.IsZero() {
|
||||||
|
query += " AND `timestamp` >= ?"
|
||||||
|
args = append(args, startDate)
|
||||||
|
}
|
||||||
|
if !endDate.IsZero() {
|
||||||
|
query += " AND `timestamp` <= ?"
|
||||||
|
args = append(args, endDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
query += " ORDER BY `timestamp` DESC"
|
||||||
|
if limit > 0 {
|
||||||
|
query += " LIMIT ?"
|
||||||
|
args = append(args, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := db.Query(query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var result []SensorData
|
||||||
|
for rows.Next() {
|
||||||
|
var data SensorData
|
||||||
|
var xInt, yInt, zInt, tempInt int
|
||||||
|
if err := rows.Scan(&data.ID, &data.SensorID, &xInt, &yInt, &zInt, &tempInt, &data.Timestamp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data.X = float64(xInt) / SCALING_FACTOR
|
||||||
|
data.Y = float64(yInt) / SCALING_FACTOR
|
||||||
|
data.Z = float64(zInt) / SCALING_FACTOR
|
||||||
|
data.Temperature = float64(tempInt) / SCALING_FACTOR
|
||||||
|
result = append(result, data)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -9,17 +9,20 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 启动HTTP服务器
|
// 启动HTTP服务器
|
||||||
func StartHTTPServer(address string) error {
|
func StartHTTPServer(address string) error {
|
||||||
http.HandleFunc("/", handleIndex)
|
http.HandleFunc("/", handleIndex)
|
||||||
|
// 静态资源
|
||||||
|
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||||
|
|
||||||
http.HandleFunc("/api/data", handleGetData)
|
http.HandleFunc("/api/data", handleGetData)
|
||||||
http.HandleFunc("/api/latest", handleGetLatest)
|
http.HandleFunc("/api/latest", handleGetLatest)
|
||||||
http.HandleFunc("/api/sensors", handleGetSensors)
|
http.HandleFunc("/api/sensors", handleGetSensors)
|
||||||
http.HandleFunc("/api/clients", handleGetClients)
|
http.HandleFunc("/api/devices", handleGetDevices)
|
||||||
fmt.Printf("HTTP服务器已启动,正在监听 %s\n", address)
|
fmt.Printf("HTTP服务器已启动,正在监听 %s\n", address)
|
||||||
return http.ListenAndServe(address, nil)
|
return http.ListenAndServe(address, nil)
|
||||||
}
|
}
|
||||||
@ -60,6 +63,7 @@ func handleGetData(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
deviceID := r.URL.Query().Get("device_id")
|
||||||
sensorIDStr := r.URL.Query().Get("sensor_id")
|
sensorIDStr := r.URL.Query().Get("sensor_id")
|
||||||
limitStr := r.URL.Query().Get("limit")
|
limitStr := r.URL.Query().Get("limit")
|
||||||
startDateStr := r.URL.Query().Get("start_date")
|
startDateStr := r.URL.Query().Get("start_date")
|
||||||
@ -93,9 +97,11 @@ func handleGetData(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用北京时间解析前端传来的本地时间
|
||||||
var startDate, endDate time.Time
|
var startDate, endDate time.Time
|
||||||
|
shLoc, _ := time.LoadLocation("Asia/Shanghai")
|
||||||
if startDateStr != "" {
|
if startDateStr != "" {
|
||||||
startDate, err = time.Parse("2006-01-02T15:04", startDateStr)
|
startDate, err = time.ParseInLocation("2006-01-02T15:04", startDateStr, shLoc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("错误: 无效的开始日期: %s, %v", startDateStr, err)
|
log.Printf("错误: 无效的开始日期: %s, %v", startDateStr, err)
|
||||||
http.Error(w, "无效的开始日期", http.StatusBadRequest)
|
http.Error(w, "无效的开始日期", http.StatusBadRequest)
|
||||||
@ -104,7 +110,7 @@ func handleGetData(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if endDateStr != "" {
|
if endDateStr != "" {
|
||||||
endDate, err = time.Parse("2006-01-02T15:04", endDateStr)
|
endDate, err = time.ParseInLocation("2006-01-02T15:04", endDateStr, shLoc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("错误: 无效的结束日期: %s, %v", endDateStr, err)
|
log.Printf("错误: 无效的结束日期: %s, %v", endDateStr, err)
|
||||||
http.Error(w, "无效的结束日期", http.StatusBadRequest)
|
http.Error(w, "无效的结束日期", http.StatusBadRequest)
|
||||||
@ -112,18 +118,19 @@ func handleGetData(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 特殊处理获取最新数据的请求
|
// 业务要求:必须提供 device_id;在设备下可选指定探头
|
||||||
if limit == 1 && sensorIDStr == "" {
|
if strings.TrimSpace(deviceID) == "" {
|
||||||
log.Printf("检测到获取最新数据请求 (limit=1)")
|
http.Error(w, "必须提供 device_id", http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var data []SensorData
|
var data []SensorData
|
||||||
if sensorIDStr == "all" || sensorIDStr == "" {
|
if sensorIDStr == "all" || sensorIDStr == "" {
|
||||||
data, err = GetAllSensorData(limit, startDate, endDate)
|
data, err = GetSensorDataByDevice(deviceID, limit, startDate, endDate)
|
||||||
log.Printf("查询所有传感器数据,limit=%d, 结果数量=%d", limit, len(data))
|
log.Printf("查询设备%s下所有传感器数据,limit=%d, 结果数量=%d", deviceID, limit, len(data))
|
||||||
} else {
|
} else {
|
||||||
data, err = GetSensorData(sensorID, limit, startDate, endDate)
|
data, err = GetSensorDataByDeviceAndSensor(deviceID, sensorID, limit, startDate, endDate)
|
||||||
log.Printf("查询传感器%d数据,limit=%d, 结果数量=%d", sensorID, limit, len(data))
|
log.Printf("查询设备%s下传感器%d数据,limit=%d, 结果数量=%d", deviceID, sensorID, limit, len(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -156,8 +163,16 @@ func handleGetSensors(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Printf("接收到获取传感器列表请求")
|
log.Printf("接收到获取传感器列表请求")
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
deviceID := r.URL.Query().Get("device_id")
|
||||||
sensorIDs, err := GetAllSensorIDs()
|
var (
|
||||||
|
sensorIDs []int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if strings.TrimSpace(deviceID) != "" {
|
||||||
|
sensorIDs, err = GetSensorIDsByDevice(deviceID)
|
||||||
|
} else {
|
||||||
|
sensorIDs, err = GetAllSensorIDs()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("错误: 获取传感器ID失败: %v", err)
|
log.Printf("错误: 获取传感器ID失败: %v", err)
|
||||||
http.Error(w, "获取传感器ID失败:"+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "获取传感器ID失败:"+err.Error(), http.StatusInternalServerError)
|
||||||
@ -172,13 +187,52 @@ func handleGetSensors(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetClients(w http.ResponseWriter, r *http.Request) {
|
// 已移除 /api/clients,在线统计改由 /api/devices 提供(3小时窗口)
|
||||||
|
|
||||||
|
// 处理获取设备列表及统计的API
|
||||||
|
func handleGetDevices(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
clients := getAllClients()
|
list, err := GetDevicesWithStats()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("错误: 获取设备列表失败: %v", err)
|
||||||
|
http.Error(w, "获取设备列表失败:"+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := json.NewEncoder(w).Encode(clients); err != nil {
|
// 构造响应,计算是否在线(3小时内有上报)
|
||||||
log.Printf("错误: 编码客户端信息JSON失败: %v", err)
|
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)
|
http.Error(w, "编码JSON失败:"+err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
static/css/tailwind.min.css
vendored
Normal file
1
static/css/tailwind.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
14
static/js/chart.js
Normal file
14
static/js/chart.js
Normal file
File diff suppressed because one or more lines are too long
1686
templates/index.html
1686
templates/index.html
File diff suppressed because it is too large
Load Diff
@ -19,7 +19,7 @@ func StartUDPServer(address string) error {
|
|||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
startClientCleanup()
|
// 已移除客户端在线追踪;改由 DB 的 last_seen 统计在线
|
||||||
|
|
||||||
Logger.Printf("UDP服务器已启动,正在监听 %s\n", address)
|
Logger.Printf("UDP服务器已启动,正在监听 %s\n", address)
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ func StartUDPServer(address string) error {
|
|||||||
func handleUDPPacket(conn *net.UDPConn, addr *net.UDPAddr, data []byte) {
|
func handleUDPPacket(conn *net.UDPConn, addr *net.UDPAddr, data []byte) {
|
||||||
remoteAddr := addr.String()
|
remoteAddr := addr.String()
|
||||||
|
|
||||||
addClient(remoteAddr)
|
// 已移除客户端在线追踪
|
||||||
|
|
||||||
rawData := string(data)
|
rawData := string(data)
|
||||||
TCPDataLogger.Printf("从UDP客户端 %s 接收到原始数据: %s", remoteAddr, rawData)
|
TCPDataLogger.Printf("从UDP客户端 %s 接收到原始数据: %s", remoteAddr, rawData)
|
||||||
@ -102,5 +102,5 @@ func handleUDPPacket(conn *net.UDPConn, addr *net.UDPAddr, data []byte) {
|
|||||||
Logger.Printf("发送响应到UDP客户端 %s 失败: %v", remoteAddr, err)
|
Logger.Printf("发送响应到UDP客户端 %s 失败: %v", remoteAddr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateClientLastSeen(remoteAddr)
|
// 已移除客户端在线追踪
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user