diff --git a/http_server.go b/http_server.go index 8671b01..3fbd8d7 100644 --- a/http_server.go +++ b/http_server.go @@ -1,168 +1,194 @@ -package main - -import ( - "encoding/json" - "fmt" - "html/template" - "log" - "net/http" - "os" - "path/filepath" - "strconv" - "time" -) - -// 启动HTTP服务器 -func StartHTTPServer(address string) error { - http.HandleFunc("/", handleIndex) - - http.HandleFunc("/api/data", handleGetData) - http.HandleFunc("/api/sensors", handleGetSensors) - http.HandleFunc("/api/clients", handleGetClients) - 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") - - 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 - if startDateStr != "" { - startDate, err = time.Parse("2006-01-02T15:04", startDateStr) - if err != nil { - log.Printf("错误: 无效的开始日期: %s, %v", startDateStr, err) - http.Error(w, "无效的开始日期", http.StatusBadRequest) - return - } - } - - if endDateStr != "" { - endDate, err = time.Parse("2006-01-02T15:04", endDateStr) - if err != nil { - log.Printf("错误: 无效的结束日期: %s, %v", endDateStr, err) - http.Error(w, "无效的结束日期", http.StatusBadRequest) - return - } - } - - var data []SensorData - if sensorIDStr == "all" || sensorIDStr == "" { - data, err = GetAllSensorData(limit, startDate, endDate) - } else { - data, err = GetSensorData(sensorID, limit, startDate, endDate) - } - - if err != nil { - log.Printf("错误: 获取数据失败: %v", err) - http.Error(w, "获取数据失败:"+err.Error(), http.StatusInternalServerError) - return - } - - 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") - - 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) - } -} -func handleGetClients(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - clients := getAllClients() - - if err := json.NewEncoder(w).Encode(clients); err != nil { - log.Printf("错误: 编码客户端信息JSON失败: %v", err) - http.Error(w, "编码JSON失败:"+err.Error(), http.StatusInternalServerError) - } -} +package main + +import ( + "encoding/json" + "fmt" + "html/template" + "log" + "net/http" + "os" + "path/filepath" + "strconv" + "time" +) + +// 启动HTTP服务器 +func StartHTTPServer(address string) error { + http.HandleFunc("/", handleIndex) + + http.HandleFunc("/api/data", handleGetData) + http.HandleFunc("/api/sensors", handleGetSensors) + http.HandleFunc("/api/clients", handleGetClients) + http.HandleFunc("/api/trigger-query", handleTriggerQuery) + 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") + + 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 + if startDateStr != "" { + startDate, err = time.Parse("2006-01-02T15:04", startDateStr) + if err != nil { + log.Printf("错误: 无效的开始日期: %s, %v", startDateStr, err) + http.Error(w, "无效的开始日期", http.StatusBadRequest) + return + } + } + + if endDateStr != "" { + endDate, err = time.Parse("2006-01-02T15:04", endDateStr) + if err != nil { + log.Printf("错误: 无效的结束日期: %s, %v", endDateStr, err) + http.Error(w, "无效的结束日期", http.StatusBadRequest) + return + } + } + + var data []SensorData + if sensorIDStr == "all" || sensorIDStr == "" { + data, err = GetAllSensorData(limit, startDate, endDate) + } else { + data, err = GetSensorData(sensorID, limit, startDate, endDate) + } + + if err != nil { + log.Printf("错误: 获取数据失败: %v", err) + http.Error(w, "获取数据失败:"+err.Error(), http.StatusInternalServerError) + return + } + + 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") + + 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) + } +} + +func handleGetClients(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + clients := getAllClients() + + if err := json.NewEncoder(w).Encode(clients); err != nil { + log.Printf("错误: 编码客户端信息JSON失败: %v", err) + http.Error(w, "编码JSON失败:"+err.Error(), http.StatusInternalServerError) + } +} + +// 处理手动触发查询指令的API +func handleTriggerQuery(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Method != "POST" { + http.Error(w, "只支持POST请求", http.StatusMethodNotAllowed) + return + } + + // 触发向所有在线客户端发送查询指令 + sent := triggerManualQuery() + + response := map[string]interface{}{ + "success": true, + "message": fmt.Sprintf("已向%d个客户端发送查询指令", sent), + "sent_count": sent, + } + + if err := json.NewEncoder(w).Encode(response); err != nil { + log.Printf("错误: 编码响应JSON失败: %v", err) + http.Error(w, "编码JSON失败:"+err.Error(), http.StatusInternalServerError) + } +} diff --git a/tcp_server.go b/tcp_server.go index fdfd73f..a376666 100644 --- a/tcp_server.go +++ b/tcp_server.go @@ -22,6 +22,7 @@ type ClientInfo struct { var ( clientsMutex sync.Mutex clients = make(map[string]*ClientInfo) + clientConns = make(map[string]net.Conn) // 存储客户端连接 ) // StartTCPServer 启动TCP服务器 @@ -55,6 +56,11 @@ func handleConnection(conn net.Conn) { addClient(remoteAddr) + // 存储连接以便手动查询使用 + clientsMutex.Lock() + clientConns[remoteAddr] = conn + clientsMutex.Unlock() + // 启动定时发送指令的goroutine go sendPeriodicCommand(conn, remoteAddr) @@ -69,6 +75,10 @@ func handleConnection(conn net.Conn) { Logger.Printf("客户端断开连接 %s", remoteAddr) } removeClient(remoteAddr) + // 清理连接映射 + clientsMutex.Lock() + delete(clientConns, remoteAddr) + clientsMutex.Unlock() break } @@ -92,6 +102,10 @@ func handleConnection(conn net.Conn) { if _, err := conn.Write([]byte(resp)); err != nil { Logger.Printf("发送响应到客户端 %s 失败: %v", remoteAddr, err) removeClient(remoteAddr) + // 清理连接映射 + clientsMutex.Lock() + delete(clientConns, remoteAddr) + clientsMutex.Unlock() break } @@ -282,6 +296,7 @@ func startClientCleanup() { for addr, client := range clients { if now.Sub(client.LastSeen) > 24*time.Hour { delete(clients, addr) + delete(clientConns, addr) // 同时清理连接 Logger.Printf("移除过期客户端: %s", addr) } } @@ -290,3 +305,31 @@ func startClientCleanup() { } }() } + +// triggerManualQuery 手动触发向所有在线客户端发送查询指令 +func triggerManualQuery() int { + clientsMutex.Lock() + defer clientsMutex.Unlock() + + sentCount := 0 + command := "@1602301014A*0!\n" + + for addr, client := range clients { + // 检查客户端是否在线(最近10分钟内活跃) + if time.Since(client.LastSeen) < 10*time.Minute { + if conn, exists := clientConns[addr]; exists { + if _, err := conn.Write([]byte(command)); err != nil { + Logger.Printf("手动发送查询指令到客户端 %s 失败: %v", addr, err) + // 连接可能已断开,从映射中移除 + delete(clientConns, addr) + } else { + TCPDataLogger.Printf("手动发送查询指令到客户端 %s: %s", addr, strings.TrimSpace(command)) + sentCount++ + } + } + } + } + + Logger.Printf("手动查询指令已发送到 %d 个客户端", sentCount) + return sentCount +} diff --git a/templates/index.html b/templates/index.html index c70bb05..7c0c6a5 100644 --- a/templates/index.html +++ b/templates/index.html @@ -275,6 +275,9 @@ // 检查连接状态 checkConnectionStatus(); connectionCheckTimer = setInterval(checkConnectionStatus, 30000); // 每30秒检查一次 + + // 显示数据库中的最新数据(不发送TCP查询指令) + fetchLatestData(); }); // 初始化日期选择器 @@ -784,13 +787,35 @@ // 先获取一次当前最新数据 fetchLatestData(); - // 触发TCP服务器发送查询指令,等待3秒后再次获取最新数据 - setTimeout(() => { - fetchLatestData(); - loadData(); // 也刷新一下表格数据 + // 发送TCP查询指令 + fetch('/api/trigger-query', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + } + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + console.log(`查询指令已发送到 ${data.sent_count} 个设备`); + // 等待3秒后获取最新数据(给设备响应时间) + setTimeout(() => { + fetchLatestData(); + loadData(); // 也刷新一下表格数据 + latestDataElement.style.opacity = 1; + document.getElementById('queryLatestBtn').textContent = '查询最新数据'; + }, 3000); + } else { + console.error('发送查询指令失败:', data.message); + latestDataElement.style.opacity = 1; + document.getElementById('queryLatestBtn').textContent = '查询最新数据'; + } + }) + .catch(error => { + console.error('发送查询指令出错:', error); latestDataElement.style.opacity = 1; document.getElementById('queryLatestBtn').textContent = '查询最新数据'; - }, 3000); + }); } // 页面卸载时清除定时器