feat: 新增all选项

This commit is contained in:
fengyarnom 2025-05-18 14:11:45 +08:00
parent 49aee3a2ed
commit bc4af6be86
15 changed files with 1799 additions and 1788 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
logs/ logs/

16
.idea/.gitignore generated vendored
View File

@ -1,8 +1,8 @@
# Default ignored files # Default ignored files
/shelf/ /shelf/
/workspace.xml /workspace.xml
# Editor-based HTTP Client requests # Editor-based HTTP Client requests
/httpRequests/ /httpRequests/
# Datasource local storage ignored files # Datasource local storage ignored files
/dataSources/ /dataSources/
/dataSources.local.xml /dataSources.local.xml

16
.idea/angle_dtu.iml generated
View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4"> <module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" /> <component name="Go" enabled="true" />
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

14
.idea/modules.xml generated
View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/.idea/angle_dtu.iml" filepath="$PROJECT_DIR$/.idea/angle_dtu.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/angle_dtu.iml" filepath="$PROJECT_DIR$/.idea/angle_dtu.iml" />
</modules> </modules>
</component> </component>
</project> </project>

10
.idea/vcs.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" /> <mapping directory="" vcs="Git" />
</component> </component>
</project> </project>

View File

@ -1,66 +1,66 @@
package main package main
import ( import (
"fmt" "fmt"
"io" "io"
"log" "log"
"net" "net"
"time" "time"
) )
func handleConnection(conn net.Conn) { func handleConnection(conn net.Conn) {
defer conn.Close() defer conn.Close()
// 客户端地址信息 // 客户端地址信息
remoteAddr := conn.RemoteAddr().String() remoteAddr := conn.RemoteAddr().String()
fmt.Printf("客户端 %s 已连接\n", remoteAddr) fmt.Printf("客户端 %s 已连接\n", remoteAddr)
// 设置连接超时 // 设置连接超时
conn.SetReadDeadline(time.Now().Add(time.Hour * 24)) // 24小时超时可以根据需要调整 conn.SetReadDeadline(time.Now().Add(time.Hour * 24)) // 24小时超时可以根据需要调整
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
for { for {
// 读取客户端发送的数据 // 读取客户端发送的数据
n, err := conn.Read(buffer) n, err := conn.Read(buffer)
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
fmt.Printf("客户端 %s 已断开连接\n", remoteAddr) fmt.Printf("客户端 %s 已断开连接\n", remoteAddr)
} else { } else {
fmt.Printf("从客户端 %s 读取数据时出错: %v\n", remoteAddr, err) fmt.Printf("从客户端 %s 读取数据时出错: %v\n", remoteAddr, err)
} }
break break
} }
// 在终端显示接收到的数据 // 在终端显示接收到的数据
receivedData := buffer[:n] receivedData := buffer[:n]
fmt.Printf("从 %s 接收到数据: %s\n", remoteAddr, string(receivedData)) fmt.Printf("从 %s 接收到数据: %s\n", remoteAddr, string(receivedData))
// 可选:回复客户端确认消息 // 可选:回复客户端确认消息
// conn.Write([]byte("服务器已收到消息")) // conn.Write([]byte("服务器已收到消息"))
} }
} }
func main() { func main() {
// 监听端口 // 监听端口
port := ":10002" port := ":10002"
listener, err := net.Listen("tcp", port) listener, err := net.Listen("tcp", port)
if err != nil { if err != nil {
log.Fatalf("无法监听端口 %s: %v", port, err) log.Fatalf("无法监听端口 %s: %v", port, err)
} }
defer listener.Close() defer listener.Close()
fmt.Printf("TCP服务器已启动正在监听端口 %s\n", port) fmt.Printf("TCP服务器已启动正在监听端口 %s\n", port)
// 接受连接并处理 // 接受连接并处理
for { for {
conn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
fmt.Printf("接受连接失败: %v\n", err) fmt.Printf("接受连接失败: %v\n", err)
continue continue
} }
// 为每个连接创建一个goroutine // 为每个连接创建一个goroutine
go handleConnection(conn) go handleConnection(conn)
} }
} }

18
db.go
View File

@ -79,8 +79,13 @@ func GetSensorData(sensorID int, limit int, startDate time.Time, endDate time.Ti
args = append(args, endDate) args = append(args, endDate)
} }
query += " ORDER BY timestamp DESC LIMIT ?" query += " ORDER BY timestamp DESC"
args = append(args, limit)
// 只有当limit > 0时才添加LIMIT子句
if limit > 0 {
query += " LIMIT ?"
args = append(args, limit)
}
rows, err := db.Query(query, args...) rows, err := db.Query(query, args...)
if err != nil { if err != nil {
@ -128,8 +133,13 @@ func GetAllSensorData(limit int, startDate time.Time, endDate time.Time) ([]Sens
args = append(args, endDate) args = append(args, endDate)
} }
query += " ORDER BY timestamp DESC LIMIT ?" query += " ORDER BY timestamp DESC"
args = append(args, limit)
// 只有当limit > 0时才添加LIMIT子句
if limit > 0 {
query += " LIMIT ?"
args = append(args, limit)
}
rows, err := db.Query(query, args...) rows, err := db.Query(query, args...)
if err != nil { if err != nil {

14
go.mod
View File

@ -1,7 +1,7 @@
module probe-monitor module probe-monitor
go 1.24.2 go 1.24.2
require github.com/go-sql-driver/mysql v1.9.2 require github.com/go-sql-driver/mysql v1.9.2
require filippo.io/edwards25519 v1.1.0 // indirect require filippo.io/edwards25519 v1.1.0 // indirect

8
go.sum
View File

@ -1,4 +1,4 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU= github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=

View File

@ -1,168 +1,168 @@
package main package main
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"html/template" "html/template"
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"time" "time"
) )
// 启动HTTP服务器 // 启动HTTP服务器
func StartHTTPServer(address string) error { func StartHTTPServer(address string) error {
http.HandleFunc("/", handleIndex) http.HandleFunc("/", handleIndex)
http.HandleFunc("/api/data", handleGetData) http.HandleFunc("/api/data", handleGetData)
http.HandleFunc("/api/sensors", handleGetSensors) http.HandleFunc("/api/sensors", handleGetSensors)
http.HandleFunc("/api/clients", handleGetClients) http.HandleFunc("/api/clients", handleGetClients)
fmt.Printf("HTTP服务器已启动正在监听 %s\n", address) fmt.Printf("HTTP服务器已启动正在监听 %s\n", address)
return http.ListenAndServe(address, nil) return http.ListenAndServe(address, nil)
} }
// 处理主页 // 处理主页
func handleIndex(w http.ResponseWriter, r *http.Request) { func handleIndex(w http.ResponseWriter, r *http.Request) {
log.Printf("接收到主页请求: %s", r.URL.Path) log.Printf("接收到主页请求: %s", r.URL.Path)
templatePath := "templates/index.html" templatePath := "templates/index.html"
absPath, _ := filepath.Abs(templatePath) absPath, _ := filepath.Abs(templatePath)
_, err := os.Stat(templatePath) _, err := os.Stat(templatePath)
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.Printf("错误: 模板文件不存在: %s", absPath) log.Printf("错误: 模板文件不存在: %s", absPath)
http.Error(w, "模板文件不存在", http.StatusInternalServerError) http.Error(w, "模板文件不存在", http.StatusInternalServerError)
return return
} }
tmpl, err := template.ParseFiles(templatePath) tmpl, err := template.ParseFiles(templatePath)
if err != nil { if err != nil {
log.Printf("错误: 无法解析模板: %v", err) log.Printf("错误: 无法解析模板: %v", err)
http.Error(w, "无法加载模板:"+err.Error(), http.StatusInternalServerError) http.Error(w, "无法加载模板:"+err.Error(), http.StatusInternalServerError)
return return
} }
log.Printf("模板加载成功,开始渲染") log.Printf("模板加载成功,开始渲染")
err = tmpl.Execute(w, nil) err = tmpl.Execute(w, nil)
if err != nil { if err != nil {
log.Printf("错误: 渲染模板出错: %v", err) log.Printf("错误: 渲染模板出错: %v", err)
http.Error(w, "渲染模板出错:"+err.Error(), http.StatusInternalServerError) http.Error(w, "渲染模板出错:"+err.Error(), http.StatusInternalServerError)
} }
log.Printf("模板渲染完成") log.Printf("模板渲染完成")
} }
// 处理获取传感器数据的API // 处理获取传感器数据的API
func handleGetData(w http.ResponseWriter, r *http.Request) { func handleGetData(w http.ResponseWriter, r *http.Request) {
log.Printf("接收到获取数据请求: %s", r.URL.String()) log.Printf("接收到获取数据请求: %s", r.URL.String())
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
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")
endDateStr := r.URL.Query().Get("end_date") endDateStr := r.URL.Query().Get("end_date")
limit := 500 // 默认限制为500条数据 limit := 500 // 默认限制为500条数据
noLimit := false // 是否不限制数据条数 noLimit := false // 是否不限制数据条数
var sensorID int var sensorID int
var err error var err error
if sensorIDStr != "" && sensorIDStr != "all" { if sensorIDStr != "" && sensorIDStr != "all" {
sensorID, err = strconv.Atoi(sensorIDStr) sensorID, err = strconv.Atoi(sensorIDStr)
if err != nil { if err != nil {
log.Printf("错误: 无效的传感器ID: %s", sensorIDStr) log.Printf("错误: 无效的传感器ID: %s", sensorIDStr)
http.Error(w, "无效的传感器ID", http.StatusBadRequest) http.Error(w, "无效的传感器ID", http.StatusBadRequest)
return return
} }
} }
if limitStr != "" { if limitStr != "" {
if limitStr == "all" { if limitStr == "all" {
noLimit = true noLimit = true
limit = 1000000 // 使用一个非常大的数值作为实际上的"无限制" limit = 0 // 设置为0表示不使用LIMIT子句
} else { } else {
limit, err = strconv.Atoi(limitStr) limit, err = strconv.Atoi(limitStr)
if err != nil || limit <= 0 { if err != nil || limit <= 0 {
log.Printf("错误: 无效的记录数限制: %s", limitStr) log.Printf("错误: 无效的记录数限制: %s", limitStr)
http.Error(w, "无效的记录数限制", http.StatusBadRequest) http.Error(w, "无效的记录数限制", http.StatusBadRequest)
return return
} }
} }
} }
var startDate, endDate time.Time var startDate, endDate time.Time
if startDateStr != "" { if startDateStr != "" {
startDate, err = time.Parse("2006-01-02T15:04", startDateStr) startDate, err = time.Parse("2006-01-02T15:04", startDateStr)
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)
return return
} }
} }
if endDateStr != "" { if endDateStr != "" {
endDate, err = time.Parse("2006-01-02T15:04", endDateStr) endDate, err = time.Parse("2006-01-02T15:04", endDateStr)
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)
return return
} }
} }
var data []SensorData var data []SensorData
if sensorIDStr == "all" || sensorIDStr == "" { if sensorIDStr == "all" || sensorIDStr == "" {
data, err = GetAllSensorData(limit, startDate, endDate) data, err = GetAllSensorData(limit, startDate, endDate)
} else { } else {
data, err = GetSensorData(sensorID, limit, startDate, endDate) data, err = GetSensorData(sensorID, limit, startDate, endDate)
} }
if err != nil { if err != nil {
log.Printf("错误: 获取数据失败: %v", err) log.Printf("错误: 获取数据失败: %v", err)
http.Error(w, "获取数据失败:"+err.Error(), http.StatusInternalServerError) http.Error(w, "获取数据失败:"+err.Error(), http.StatusInternalServerError)
return return
} }
if noLimit { if noLimit {
log.Printf("成功获取到所有数据记录(%d条", len(data)) log.Printf("成功获取到所有数据记录(%d条", len(data))
} else { } else {
log.Printf("成功获取到 %d 条数据记录(限制:%d条", len(data), limit) log.Printf("成功获取到 %d 条数据记录(限制:%d条", len(data), limit)
} }
if err := json.NewEncoder(w).Encode(data); err != nil { if err := json.NewEncoder(w).Encode(data); err != nil {
log.Printf("错误: 编码JSON失败: %v", err) log.Printf("错误: 编码JSON失败: %v", err)
http.Error(w, "编码JSON失败"+err.Error(), http.StatusInternalServerError) http.Error(w, "编码JSON失败"+err.Error(), http.StatusInternalServerError)
} }
} }
// 处理获取所有传感器ID的API // 处理获取所有传感器ID的API
func handleGetSensors(w http.ResponseWriter, r *http.Request) { 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")
sensorIDs, err := GetAllSensorIDs() 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)
return return
} }
log.Printf("成功获取到 %d 个传感器ID", len(sensorIDs)) log.Printf("成功获取到 %d 个传感器ID", len(sensorIDs))
if err := json.NewEncoder(w).Encode(sensorIDs); err != nil { if err := json.NewEncoder(w).Encode(sensorIDs); err != nil {
log.Printf("错误: 编码JSON失败: %v", err) log.Printf("错误: 编码JSON失败: %v", err)
http.Error(w, "编码JSON失败"+err.Error(), http.StatusInternalServerError) http.Error(w, "编码JSON失败"+err.Error(), http.StatusInternalServerError)
} }
} }
func handleGetClients(w http.ResponseWriter, r *http.Request) { func handleGetClients(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
clients := getAllClients() clients := getAllClients()
if err := json.NewEncoder(w).Encode(clients); err != nil { if err := json.NewEncoder(w).Encode(clients); err != nil {
log.Printf("错误: 编码客户端信息JSON失败: %v", err) log.Printf("错误: 编码客户端信息JSON失败: %v", err)
http.Error(w, "编码JSON失败"+err.Error(), http.StatusInternalServerError) http.Error(w, "编码JSON失败"+err.Error(), http.StatusInternalServerError)
} }
} }

158
logger.go
View File

@ -1,79 +1,79 @@
package main package main
import ( import (
"fmt" "fmt"
"io" "io"
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
) )
var ( var (
logFile *os.File logFile *os.File
Logger *log.Logger // 导出Logger供其他包使用 Logger *log.Logger // 导出Logger供其他包使用
TCPDataLogger *log.Logger // 专门用于记录TCP数据的日志 TCPDataLogger *log.Logger // 专门用于记录TCP数据的日志
) )
// 初始化日志系统 // 初始化日志系统
func InitLogger() error { func InitLogger() error {
logsDir := "logs" logsDir := "logs"
if err := os.MkdirAll(logsDir, 0755); err != nil { if err := os.MkdirAll(logsDir, 0755); err != nil {
return fmt.Errorf("创建日志目录失败: %v", err) return fmt.Errorf("创建日志目录失败: %v", err)
} }
today := time.Now().Format("2006-01-02") today := time.Now().Format("2006-01-02")
logFilePath := filepath.Join(logsDir, fmt.Sprintf("server_%s.log", today)) logFilePath := filepath.Join(logsDir, fmt.Sprintf("server_%s.log", today))
file, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) file, err := os.OpenFile(logFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
return fmt.Errorf("打开日志文件失败: %v", err) return fmt.Errorf("打开日志文件失败: %v", err)
} }
logFile = file logFile = file
multiWriter := io.MultiWriter(os.Stdout, file) multiWriter := io.MultiWriter(os.Stdout, file)
Logger = log.New(multiWriter, "", log.Ldate|log.Ltime|log.Lshortfile) Logger = log.New(multiWriter, "", log.Ldate|log.Ltime|log.Lshortfile)
tcpDataFilePath := filepath.Join(logsDir, fmt.Sprintf("tcp_data_%s.log", today)) tcpDataFilePath := filepath.Join(logsDir, fmt.Sprintf("tcp_data_%s.log", today))
tcpDataFile, err := os.OpenFile(tcpDataFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) tcpDataFile, err := os.OpenFile(tcpDataFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
return fmt.Errorf("打开TCP数据日志文件失败: %v", err) return fmt.Errorf("打开TCP数据日志文件失败: %v", err)
} }
tcpDataMultiWriter := io.MultiWriter(os.Stdout, tcpDataFile) tcpDataMultiWriter := io.MultiWriter(os.Stdout, tcpDataFile)
TCPDataLogger = log.New(tcpDataMultiWriter, "TCP_DATA: ", log.Ldate|log.Ltime) TCPDataLogger = log.New(tcpDataMultiWriter, "TCP_DATA: ", log.Ldate|log.Ltime)
log.SetOutput(multiWriter) log.SetOutput(multiWriter)
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
Logger.Println("日志系统初始化完成") Logger.Println("日志系统初始化完成")
return nil return nil
} }
// 关闭日志文件 // 关闭日志文件
func CloseLogger() { func CloseLogger() {
if logFile != nil { if logFile != nil {
logFile.Close() logFile.Close()
} }
} }
// 日志轮转,每天创建新的日志文件 // 日志轮转,每天创建新的日志文件
func StartLogRotation() { func StartLogRotation() {
go func() { go func() {
for { for {
now := time.Now() now := time.Now()
next := now.Add(24 * time.Hour) next := now.Add(24 * time.Hour)
next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location()) next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, next.Location())
duration := next.Sub(now) duration := next.Sub(now)
time.Sleep(duration) time.Sleep(duration)
Logger.Println("开始日志轮转...") Logger.Println("开始日志轮转...")
CloseLogger() CloseLogger()
if err := InitLogger(); err != nil { if err := InitLogger(); err != nil {
log.Printf("日志轮转失败: %v", err) log.Printf("日志轮转失败: %v", err)
} }
} }
}() }()
} }

View File

@ -1,99 +1,99 @@
/* 基本样式和Flex布局 */ /* 基本样式和Flex布局 */
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.header { .header {
padding: 10px; padding: 10px;
text-align: center; text-align: center;
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
} }
.container { .container {
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
padding: 15px; padding: 15px;
} }
.controls { .controls {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 10px; gap: 10px;
margin-bottom: 20px; margin-bottom: 20px;
padding: 10px; padding: 10px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 5px; border-radius: 5px;
} }
.control-group { .control-group {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 5px; gap: 5px;
} }
select, input, button { select, input, button {
padding: 5px; padding: 5px;
} }
button { button {
cursor: pointer; cursor: pointer;
} }
.chart-container { .chart-container {
margin-bottom: 20px; margin-bottom: 20px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 5px; border-radius: 5px;
padding: 10px; padding: 10px;
} }
canvas { canvas {
width: 100%; width: 100%;
max-height: 400px; max-height: 400px;
} }
.table-container { .table-container {
overflow-x: auto; overflow-x: auto;
margin-bottom: 20px; margin-bottom: 20px;
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 5px; border-radius: 5px;
padding: 10px; padding: 10px;
} }
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
} }
th, td { th, td {
border: 1px solid #ddd; border: 1px solid #ddd;
padding: 8px; padding: 8px;
text-align: left; text-align: left;
} }
th { th {
background-color: #f8f8f8; background-color: #f8f8f8;
} }
tr:nth-child(even) { tr:nth-child(even) {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
.footer { .footer {
text-align: center; text-align: center;
padding: 10px; padding: 10px;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
} }
/* 响应式设计 */ /* 响应式设计 */
@media (max-width: 768px) { @media (max-width: 768px) {
.controls { .controls {
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
} }
.control-group { .control-group {
justify-content: space-between; justify-content: space-between;
} }
} }

View File

@ -1,431 +1,431 @@
let sensorChart = null; let sensorChart = null;
let refreshInterval = null; let refreshInterval = null;
let allSensors = []; let allSensors = [];
let currentSensorData = []; let currentSensorData = [];
// 页面加载完成后执行 // 页面加载完成后执行
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// 初始化日期选择器为今天 // 初始化日期选择器为今天
initializeDatePickers(); initializeDatePickers();
// 加载所有传感器ID // 加载所有传感器ID
loadSensors(); loadSensors();
// 添加事件监听器 // 添加事件监听器
setupEventListeners(); setupEventListeners();
// 设置自动刷新 // 设置自动刷新
setupAutoRefresh(); setupAutoRefresh();
}); });
// 初始化日期选择器 // 初始化日期选择器
function initializeDatePickers() { function initializeDatePickers() {
const now = new Date(); const now = new Date();
const today = now.toISOString().split('T')[0]; const today = now.toISOString().split('T')[0];
const time = now.toTimeString().split(' ')[0].substring(0, 5); const time = now.toTimeString().split(' ')[0].substring(0, 5);
// 设置默认的开始时间为当天00:00 // 设置默认的开始时间为当天00:00
document.getElementById('startDate').value = `${today}T00:00`; document.getElementById('startDate').value = `${today}T00:00`;
// 设置默认的结束时间为当前时间 // 设置默认的结束时间为当前时间
document.getElementById('endDate').value = `${today}T${time}`; document.getElementById('endDate').value = `${today}T${time}`;
} }
// 设置事件监听器 // 设置事件监听器
function setupEventListeners() { function setupEventListeners() {
// 查询按钮 // 查询按钮
document.getElementById('queryBtn').addEventListener('click', function() { document.getElementById('queryBtn').addEventListener('click', function() {
loadData(); loadData();
}); });
// 重置按钮 // 重置按钮
document.getElementById('resetBtn').addEventListener('click', function() { document.getElementById('resetBtn').addEventListener('click', function() {
resetFilters(); resetFilters();
}); });
// 传感器选择变化 // 传感器选择变化
document.getElementById('sensorSelect').addEventListener('change', function() { document.getElementById('sensorSelect').addEventListener('change', function() {
loadData(); loadData();
}); });
// 记录数限制变化 // 记录数限制变化
document.getElementById('limitSelect').addEventListener('change', function() { document.getElementById('limitSelect').addEventListener('change', function() {
loadData(); loadData();
}); });
// 导出CSV按钮 // 导出CSV按钮
document.getElementById('exportBtn').addEventListener('click', function() { document.getElementById('exportBtn').addEventListener('click', function() {
exportToCSV(); exportToCSV();
}); });
} }
// 设置自动刷新 // 设置自动刷新
function setupAutoRefresh() { function setupAutoRefresh() {
const autoRefreshCheckbox = document.getElementById('autoRefresh'); const autoRefreshCheckbox = document.getElementById('autoRefresh');
// 初始化自动刷新 // 初始化自动刷新
if (autoRefreshCheckbox.checked) { if (autoRefreshCheckbox.checked) {
startAutoRefresh(); startAutoRefresh();
} }
// 监听复选框变化 // 监听复选框变化
autoRefreshCheckbox.addEventListener('change', function() { autoRefreshCheckbox.addEventListener('change', function() {
if (this.checked) { if (this.checked) {
startAutoRefresh(); startAutoRefresh();
} else { } else {
stopAutoRefresh(); stopAutoRefresh();
} }
}); });
} }
// 开始自动刷新 // 开始自动刷新
function startAutoRefresh() { function startAutoRefresh() {
if (refreshInterval) { if (refreshInterval) {
clearInterval(refreshInterval); clearInterval(refreshInterval);
} }
refreshInterval = setInterval(loadData, 10000); // 10秒刷新一次 refreshInterval = setInterval(loadData, 10000); // 10秒刷新一次
} }
// 停止自动刷新 // 停止自动刷新
function stopAutoRefresh() { function stopAutoRefresh() {
if (refreshInterval) { if (refreshInterval) {
clearInterval(refreshInterval); clearInterval(refreshInterval);
refreshInterval = null; refreshInterval = null;
} }
} }
// 重置筛选条件 // 重置筛选条件
function resetFilters() { function resetFilters() {
initializeDatePickers(); initializeDatePickers();
document.getElementById('sensorSelect').value = 'all'; document.getElementById('sensorSelect').value = 'all';
document.getElementById('limitSelect').value = '100'; document.getElementById('limitSelect').value = '100';
loadData(); loadData();
} }
// 加载所有传感器ID // 加载所有传感器ID
function loadSensors() { function loadSensors() {
fetch('/api/sensors') fetch('/api/sensors')
.then(response => { .then(response => {
if (!response.ok) { if (!response.ok) {
throw new Error('获取传感器列表失败'); throw new Error('获取传感器列表失败');
} }
return response.json(); return response.json();
}) })
.then(data => { .then(data => {
allSensors = data; allSensors = data;
updateSensorSelect(data); updateSensorSelect(data);
// 加载数据 // 加载数据
loadData(); loadData();
}) })
.catch(error => { .catch(error => {
console.error('加载传感器列表出错:', error); console.error('加载传感器列表出错:', error);
alert('加载传感器列表出错: ' + error.message); alert('加载传感器列表出错: ' + error.message);
}); });
} }
// 更新传感器选择下拉框 // 更新传感器选择下拉框
function updateSensorSelect(sensors) { function updateSensorSelect(sensors) {
const select = document.getElementById('sensorSelect'); const select = document.getElementById('sensorSelect');
// 保留"所有传感器"选项 // 保留"所有传感器"选项
const allOption = select.querySelector('option[value="all"]'); const allOption = select.querySelector('option[value="all"]');
select.innerHTML = ''; select.innerHTML = '';
select.appendChild(allOption); select.appendChild(allOption);
if (sensors.length === 0) { if (sensors.length === 0) {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = ''; option.value = '';
option.textContent = '没有可用的传感器'; option.textContent = '没有可用的传感器';
select.appendChild(option); select.appendChild(option);
return; return;
} }
sensors.forEach(id => { sensors.forEach(id => {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = id; option.value = id;
option.textContent = `传感器 ${id}`; option.textContent = `传感器 ${id}`;
select.appendChild(option); select.appendChild(option);
}); });
} }
// 加载传感器数据 // 加载传感器数据
function loadData() { function loadData() {
const sensorID = document.getElementById('sensorSelect').value; const sensorID = document.getElementById('sensorSelect').value;
const limit = document.getElementById('limitSelect').value; const limit = document.getElementById('limitSelect').value;
const startDate = document.getElementById('startDate').value; const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value; const endDate = document.getElementById('endDate').value;
let url = '/api/data?'; let url = '/api/data?';
let params = []; let params = [];
// 添加查询参数 // 添加查询参数
if (sensorID !== 'all') { if (sensorID !== 'all') {
params.push(`sensor_id=${sensorID}`); params.push(`sensor_id=${sensorID}`);
} }
if (limit) { if (limit) {
params.push(`limit=${limit}`); params.push(`limit=${limit}`);
} }
if (startDate) { if (startDate) {
params.push(`start_date=${encodeURIComponent(startDate)}`); params.push(`start_date=${encodeURIComponent(startDate)}`);
} }
if (endDate) { if (endDate) {
params.push(`end_date=${encodeURIComponent(endDate)}`); params.push(`end_date=${encodeURIComponent(endDate)}`);
} }
url += params.join('&'); url += params.join('&');
// 显示加载状态 // 显示加载状态
document.getElementById('queryBtn').textContent = '加载中...'; document.getElementById('queryBtn').textContent = '加载中...';
fetch(url) fetch(url)
.then(response => { .then(response => {
if (!response.ok) { if (!response.ok) {
throw new Error('获取传感器数据失败'); throw new Error('获取传感器数据失败');
} }
return response.json(); return response.json();
}) })
.then(data => { .then(data => {
currentSensorData = data; currentSensorData = data;
updateTable(data); updateTable(data);
updateChart(data); updateChart(data);
document.getElementById('queryBtn').textContent = '查询数据'; document.getElementById('queryBtn').textContent = '查询数据';
}) })
.catch(error => { .catch(error => {
console.error('加载数据出错:', error); console.error('加载数据出错:', error);
alert('加载数据出错: ' + error.message); alert('加载数据出错: ' + error.message);
document.getElementById('queryBtn').textContent = '查询数据'; document.getElementById('queryBtn').textContent = '查询数据';
}); });
} }
// 更新数据表格 // 更新数据表格
function updateTable(data) { function updateTable(data) {
const tableBody = document.getElementById('tableBody'); const tableBody = document.getElementById('tableBody');
tableBody.innerHTML = ''; tableBody.innerHTML = '';
if (data.length === 0) { if (data.length === 0) {
const row = document.createElement('tr'); const row = document.createElement('tr');
row.innerHTML = '<td colspan="6" style="text-align: center;">没有数据</td>'; row.innerHTML = '<td colspan="6" style="text-align: center;">没有数据</td>';
tableBody.appendChild(row); tableBody.appendChild(row);
return; return;
} }
data.forEach(item => { data.forEach(item => {
const row = document.createElement('tr'); const row = document.createElement('tr');
// 解析时间并调整为中国时间UTC+8 // 解析时间并调整为中国时间UTC+8
const date = new Date(item.timestamp); const date = new Date(item.timestamp);
// 减去8小时因为数据库时间似乎比实际时间早了8小时 // 减去8小时因为数据库时间似乎比实际时间早了8小时
date.setHours(date.getHours() - 8); date.setHours(date.getHours() - 8);
// 格式化为中文日期时间格式 // 格式化为中文日期时间格式
const formattedDate = const formattedDate =
date.getFullYear() + '/' + date.getFullYear() + '/' +
(date.getMonth() + 1).toString().padStart(2, '0') + '/' + (date.getMonth() + 1).toString().padStart(2, '0') + '/' +
date.getDate().toString().padStart(2, '0') + ' ' + date.getDate().toString().padStart(2, '0') + ' ' +
date.getHours().toString().padStart(2, '0') + ':' + date.getHours().toString().padStart(2, '0') + ':' +
date.getMinutes().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0') + ':' +
date.getSeconds().toString().padStart(2, '0'); date.getSeconds().toString().padStart(2, '0');
row.innerHTML = row.innerHTML =
'<td>' + item.id + '</td>' + '<td>' + item.id + '</td>' +
'<td>' + item.sensor_id + '</td>' + '<td>' + item.sensor_id + '</td>' +
'<td>' + item.x.toFixed(3) + '</td>' + '<td>' + item.x.toFixed(3) + '</td>' +
'<td>' + item.y.toFixed(3) + '</td>' + '<td>' + item.y.toFixed(3) + '</td>' +
'<td>' + item.z.toFixed(3) + '</td>' + '<td>' + item.z.toFixed(3) + '</td>' +
'<td>' + formattedDate + '</td>'; '<td>' + formattedDate + '</td>';
tableBody.appendChild(row); tableBody.appendChild(row);
}); });
} }
// 更新图表 // 更新图表
function updateChart(data) { function updateChart(data) {
// 准备图表数据 // 准备图表数据
const chartData = prepareChartData(data); const chartData = prepareChartData(data);
// 如果图表已经存在,销毁它 // 如果图表已经存在,销毁它
if (sensorChart) { if (sensorChart) {
sensorChart.destroy(); sensorChart.destroy();
} }
// 获取图表Canvas // 获取图表Canvas
const ctx = document.getElementById('sensorChart').getContext('2d'); const ctx = document.getElementById('sensorChart').getContext('2d');
// 创建新图表 // 创建新图表
sensorChart = new Chart(ctx, { sensorChart = new Chart(ctx, {
type: 'line', type: 'line',
data: chartData, data: chartData,
options: { options: {
responsive: true, responsive: true,
maintainAspectRatio: false, maintainAspectRatio: false,
plugins: { plugins: {
title: { title: {
display: true, display: true,
text: '传感器数据趋势' text: '传感器数据趋势'
}, },
tooltip: { tooltip: {
callbacks: { callbacks: {
label: function(context) { label: function(context) {
let label = context.dataset.label || ''; let label = context.dataset.label || '';
if (label) { if (label) {
label += ': '; label += ': ';
} }
if (context.parsed.y !== null) { if (context.parsed.y !== null) {
label += context.parsed.y.toFixed(3); label += context.parsed.y.toFixed(3);
} }
return label; return label;
} }
} }
} }
}, },
scales: { scales: {
x: { x: {
title: { title: {
display: true, display: true,
text: '时间' text: '时间'
} }
}, },
y: { y: {
title: { title: {
display: true, display: true,
text: '值' text: '值'
} }
} }
} }
} }
}); });
} }
// 准备图表数据 // 准备图表数据
function prepareChartData(data) { function prepareChartData(data) {
// 如果没有数据,返回空数据集 // 如果没有数据,返回空数据集
if (data.length === 0) { if (data.length === 0) {
return { return {
labels: [], labels: [],
datasets: [] datasets: []
}; };
} }
// 反转数据以便按时间先后顺序显示 // 反转数据以便按时间先后顺序显示
const sortedData = [...data].sort((a, b) => { const sortedData = [...data].sort((a, b) => {
return new Date(a.timestamp) - new Date(b.timestamp); return new Date(a.timestamp) - new Date(b.timestamp);
}); });
// 获取所有传感器ID // 获取所有传感器ID
let sensorIDs = [...new Set(sortedData.map(item => item.sensor_id))]; let sensorIDs = [...new Set(sortedData.map(item => item.sensor_id))];
// 按传感器ID分组数据 // 按传感器ID分组数据
let datasets = []; let datasets = [];
let labels = []; let labels = [];
// 准备时间标签(使用第一个传感器的数据) // 准备时间标签(使用第一个传感器的数据)
if (sensorIDs.length > 0) { if (sensorIDs.length > 0) {
const firstSensorData = sortedData.filter(item => item.sensor_id === sensorIDs[0]); const firstSensorData = sortedData.filter(item => item.sensor_id === sensorIDs[0]);
labels = firstSensorData.map(item => { labels = firstSensorData.map(item => {
const date = new Date(item.timestamp); const date = new Date(item.timestamp);
date.setHours(date.getHours() - 8); // 调整时区 date.setHours(date.getHours() - 8); // 调整时区
return date.toLocaleString('zh-CN', { return date.toLocaleString('zh-CN', {
month: '2-digit', month: '2-digit',
day: '2-digit', day: '2-digit',
hour: '2-digit', hour: '2-digit',
minute: '2-digit' minute: '2-digit'
}); });
}); });
} }
// 定义颜色 // 定义颜色
const colors = [ const colors = [
'rgb(75, 192, 192)', 'rgb(75, 192, 192)',
'rgb(255, 99, 132)', 'rgb(255, 99, 132)',
'rgb(54, 162, 235)', 'rgb(54, 162, 235)',
'rgb(255, 205, 86)', 'rgb(255, 205, 86)',
'rgb(153, 102, 255)', 'rgb(153, 102, 255)',
'rgb(255, 159, 64)' 'rgb(255, 159, 64)'
]; ];
// 为X, Y, Z创建不同的数据集 // 为X, Y, Z创建不同的数据集
const dataTypes = [ const dataTypes = [
{ key: 'x', label: 'X值' }, { key: 'x', label: 'X值' },
{ key: 'y', label: 'Y值' }, { key: 'y', label: 'Y值' },
{ key: 'z', label: 'Z值' } { key: 'z', label: 'Z值' }
]; ];
sensorIDs.forEach((sensorID, sensorIndex) => { sensorIDs.forEach((sensorID, sensorIndex) => {
const sensorData = sortedData.filter(item => item.sensor_id === sensorID); const sensorData = sortedData.filter(item => item.sensor_id === sensorID);
dataTypes.forEach((type, typeIndex) => { dataTypes.forEach((type, typeIndex) => {
const colorIndex = (sensorIndex * dataTypes.length + typeIndex) % colors.length; const colorIndex = (sensorIndex * dataTypes.length + typeIndex) % colors.length;
datasets.push({ datasets.push({
label: `传感器${sensorID} - ${type.label}`, label: `传感器${sensorID} - ${type.label}`,
data: sensorData.map(item => item[type.key]), data: sensorData.map(item => item[type.key]),
fill: false, fill: false,
borderColor: colors[colorIndex], borderColor: colors[colorIndex],
tension: 0.1 tension: 0.1
}); });
}); });
}); });
return { return {
labels: labels, labels: labels,
datasets: datasets datasets: datasets
}; };
} }
// 导出到CSV文件 // 导出到CSV文件
function exportToCSV() { function exportToCSV() {
if (currentSensorData.length === 0) { if (currentSensorData.length === 0) {
alert('没有数据可导出'); alert('没有数据可导出');
return; return;
} }
// 准备CSV内容 // 准备CSV内容
let csvContent = "ID,传感器ID,X值,Y值,Z值,时间戳\n"; let csvContent = "ID,传感器ID,X值,Y值,Z值,时间戳\n";
currentSensorData.forEach(item => { currentSensorData.forEach(item => {
// 解析时间并调整为中国时间 // 解析时间并调整为中国时间
const date = new Date(item.timestamp); const date = new Date(item.timestamp);
date.setHours(date.getHours() - 8); date.setHours(date.getHours() - 8);
// 格式化日期 // 格式化日期
const formattedDate = const formattedDate =
date.getFullYear() + '/' + date.getFullYear() + '/' +
(date.getMonth() + 1).toString().padStart(2, '0') + '/' + (date.getMonth() + 1).toString().padStart(2, '0') + '/' +
date.getDate().toString().padStart(2, '0') + ' ' + date.getDate().toString().padStart(2, '0') + ' ' +
date.getHours().toString().padStart(2, '0') + ':' + date.getHours().toString().padStart(2, '0') + ':' +
date.getMinutes().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0') + ':' +
date.getSeconds().toString().padStart(2, '0'); date.getSeconds().toString().padStart(2, '0');
// 添加一行数据 // 添加一行数据
csvContent += csvContent +=
item.id + "," + item.id + "," +
item.sensor_id + "," + item.sensor_id + "," +
item.x.toFixed(3) + "," + item.x.toFixed(3) + "," +
item.y.toFixed(3) + "," + item.y.toFixed(3) + "," +
item.z.toFixed(3) + "," + item.z.toFixed(3) + "," +
formattedDate + "\n"; formattedDate + "\n";
}); });
// 创建Blob对象 // 创建Blob对象
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
// 创建下载链接 // 创建下载链接
const link = document.createElement("a"); const link = document.createElement("a");
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
// 设置下载属性 // 设置下载属性
link.setAttribute("href", url); link.setAttribute("href", url);
link.setAttribute("download", "sensor_data.csv"); link.setAttribute("download", "sensor_data.csv");
// 添加到文档并点击 // 添加到文档并点击
document.body.appendChild(link); document.body.appendChild(link);
link.click(); link.click();
// 清理 // 清理
document.body.removeChild(link); document.body.removeChild(link);
} }

View File

@ -1,236 +1,236 @@
package main package main
import ( import (
"fmt" "fmt"
"io" "io"
"net" "net"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
) )
// 客户端信息结构 // 客户端信息结构
type ClientInfo struct { type ClientInfo struct {
IP string // IP地址 IP string // IP地址
Port string // 端口 Port string // 端口
LastSeen time.Time // 最后活跃时间 LastSeen time.Time // 最后活跃时间
} }
// 客户端列表(使用互斥锁保护的映射) // 客户端列表(使用互斥锁保护的映射)
var ( var (
clientsMutex sync.Mutex clientsMutex sync.Mutex
clients = make(map[string]*ClientInfo) clients = make(map[string]*ClientInfo)
) )
// StartTCPServer 启动TCP服务器 // StartTCPServer 启动TCP服务器
func StartTCPServer(address string) error { func StartTCPServer(address string) error {
listener, err := net.Listen("tcp", address) listener, err := net.Listen("tcp", address)
if err != nil { if err != nil {
return err return err
} }
startClientCleanup() startClientCleanup()
Logger.Printf("TCP服务器已启动正在监听 %s\n", address) Logger.Printf("TCP服务器已启动正在监听 %s\n", address)
for { for {
conn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
Logger.Printf("接受连接失败: %v", err) Logger.Printf("接受连接失败: %v", err)
continue continue
} }
go handleConnection(conn) go handleConnection(conn)
} }
} }
// handleConnection 处理客户端连接 // handleConnection 处理客户端连接
func handleConnection(conn net.Conn) { func handleConnection(conn net.Conn) {
defer conn.Close() defer conn.Close()
remoteAddr := conn.RemoteAddr().String() remoteAddr := conn.RemoteAddr().String()
Logger.Printf("新的客户端连接: %s", remoteAddr) Logger.Printf("新的客户端连接: %s", remoteAddr)
addClient(remoteAddr) addClient(remoteAddr)
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
for { for {
n, err := conn.Read(buffer) n, err := conn.Read(buffer)
if err != nil { if err != nil {
if err != io.EOF { if err != io.EOF {
Logger.Printf("从客户端读取失败 %s: %v", remoteAddr, err) Logger.Printf("从客户端读取失败 %s: %v", remoteAddr, err)
} else { } else {
Logger.Printf("客户端断开连接 %s", remoteAddr) Logger.Printf("客户端断开连接 %s", remoteAddr)
} }
removeClient(remoteAddr) removeClient(remoteAddr)
break break
} }
rawData := string(buffer[:n]) rawData := string(buffer[:n])
TCPDataLogger.Printf("从客户端 %s 接收到原始数据: %s", remoteAddr, rawData) TCPDataLogger.Printf("从客户端 %s 接收到原始数据: %s", remoteAddr, rawData)
sensorID, x, y, z, err := parseData(rawData) sensorID, x, y, z, err := parseData(rawData)
if err == nil { if err == nil {
TCPDataLogger.Printf("解析成功 - 客户端: %s, 传感器ID: %d, 值: X=%.3f, Y=%.3f, Z=%.3f", TCPDataLogger.Printf("解析成功 - 客户端: %s, 传感器ID: %d, 值: X=%.3f, Y=%.3f, Z=%.3f",
remoteAddr, sensorID, x, y, z) remoteAddr, sensorID, x, y, z)
if err := SaveSensorData(sensorID, x, y, z); err != nil { if err := SaveSensorData(sensorID, x, y, z); err != nil {
Logger.Printf("保存传感器数据失败: %v", err) Logger.Printf("保存传感器数据失败: %v", err)
} }
} else { } else {
TCPDataLogger.Printf("无法解析从客户端 %s 接收到的数据: %s, 错误: %v", remoteAddr, rawData, err) TCPDataLogger.Printf("无法解析从客户端 %s 接收到的数据: %s, 错误: %v", remoteAddr, rawData, err)
} }
resp := "OK\n" resp := "OK\n"
if _, err := conn.Write([]byte(resp)); err != nil { if _, err := conn.Write([]byte(resp)); err != nil {
Logger.Printf("发送响应到客户端 %s 失败: %v", remoteAddr, err) Logger.Printf("发送响应到客户端 %s 失败: %v", remoteAddr, err)
removeClient(remoteAddr) removeClient(remoteAddr)
break break
} }
updateClientLastSeen(remoteAddr) updateClientLastSeen(remoteAddr)
} }
} }
// parseData 使用正则表达式解析传感器数据 // parseData 使用正则表达式解析传感器数据
func parseData(data string) (int, float64, float64, float64, error) { func parseData(data string) (int, float64, float64, float64, error) {
pattern := regexp.MustCompile(`(\d+):([-]?\d+\.\d+),\s*([-]?\d+\.\d+),\s*([-]?\d+\.\d+)`) pattern := regexp.MustCompile(`(\d+):([-]?\d+\.\d+),\s*([-]?\d+\.\d+),\s*([-]?\d+\.\d+)`)
matches := pattern.FindStringSubmatch(data) matches := pattern.FindStringSubmatch(data)
if len(matches) != 5 { if len(matches) != 5 {
return 0, 0, 0, 0, fmt.Errorf("数据格式不正确: %s", data) return 0, 0, 0, 0, fmt.Errorf("数据格式不正确: %s", data)
} }
sensorID, err := strconv.Atoi(matches[1]) sensorID, err := strconv.Atoi(matches[1])
if err != nil { if err != nil {
return 0, 0, 0, 0, fmt.Errorf("解析传感器ID失败: %v", err) return 0, 0, 0, 0, fmt.Errorf("解析传感器ID失败: %v", err)
} }
x, err := strconv.ParseFloat(strings.TrimSpace(matches[2]), 64) x, err := strconv.ParseFloat(strings.TrimSpace(matches[2]), 64)
if err != nil { if err != nil {
return 0, 0, 0, 0, fmt.Errorf("解析X值失败: %v", err) return 0, 0, 0, 0, fmt.Errorf("解析X值失败: %v", err)
} }
y, err := strconv.ParseFloat(strings.TrimSpace(matches[3]), 64) y, err := strconv.ParseFloat(strings.TrimSpace(matches[3]), 64)
if err != nil { if err != nil {
return 0, 0, 0, 0, fmt.Errorf("解析Y值失败: %v", err) return 0, 0, 0, 0, fmt.Errorf("解析Y值失败: %v", err)
} }
z, err := strconv.ParseFloat(strings.TrimSpace(matches[4]), 64) z, err := strconv.ParseFloat(strings.TrimSpace(matches[4]), 64)
if err != nil { if err != nil {
return 0, 0, 0, 0, fmt.Errorf("解析Z值失败: %v", err) return 0, 0, 0, 0, fmt.Errorf("解析Z值失败: %v", err)
} }
return sensorID, x, y, z, nil return sensorID, x, y, z, nil
} }
// addClient 添加客户端 // addClient 添加客户端
func addClient(addr string) { func addClient(addr string) {
clientsMutex.Lock() clientsMutex.Lock()
defer clientsMutex.Unlock() defer clientsMutex.Unlock()
host, port, err := net.SplitHostPort(addr) host, port, err := net.SplitHostPort(addr)
if err != nil { if err != nil {
Logger.Printf("解析客户端地址失败 %s: %v", addr, err) Logger.Printf("解析客户端地址失败 %s: %v", addr, err)
host = addr host = addr
port = "unknown" port = "unknown"
} }
clients[addr] = &ClientInfo{ clients[addr] = &ClientInfo{
IP: host, IP: host,
Port: port, Port: port,
LastSeen: time.Now(), LastSeen: time.Now(),
} }
Logger.Printf("添加新客户端: %s", addr) Logger.Printf("添加新客户端: %s", addr)
} }
// updateClientLastSeen 更新客户端最后活跃时间 // updateClientLastSeen 更新客户端最后活跃时间
func updateClientLastSeen(addr string) { func updateClientLastSeen(addr string) {
clientsMutex.Lock() clientsMutex.Lock()
defer clientsMutex.Unlock() defer clientsMutex.Unlock()
if client, exists := clients[addr]; exists { if client, exists := clients[addr]; exists {
client.LastSeen = time.Now() client.LastSeen = time.Now()
} }
} }
// removeClient 移除客户端 // removeClient 移除客户端
func removeClient(addr string) { func removeClient(addr string) {
clientsMutex.Lock() clientsMutex.Lock()
defer clientsMutex.Unlock() defer clientsMutex.Unlock()
if client, exists := clients[addr]; exists { if client, exists := clients[addr]; exists {
client.LastSeen = time.Now() client.LastSeen = time.Now()
Logger.Printf("客户端标记为断开连接: %s", addr) Logger.Printf("客户端标记为断开连接: %s", addr)
} }
} }
// getAllClients 获取所有客户端信息 // getAllClients 获取所有客户端信息
func getAllClients() []map[string]interface{} { func getAllClients() []map[string]interface{} {
clientsMutex.Lock() clientsMutex.Lock()
defer clientsMutex.Unlock() defer clientsMutex.Unlock()
now := time.Now() now := time.Now()
result := make([]map[string]interface{}, 0, len(clients)) result := make([]map[string]interface{}, 0, len(clients))
for addr, client := range clients { for addr, client := range clients {
lastSeenDuration := now.Sub(client.LastSeen) lastSeenDuration := now.Sub(client.LastSeen)
if lastSeenDuration > 24*time.Hour { if lastSeenDuration > 24*time.Hour {
delete(clients, addr) delete(clients, addr)
continue continue
} }
isOnline := lastSeenDuration < 10*time.Minute isOnline := lastSeenDuration < 10*time.Minute
result = append(result, map[string]interface{}{ result = append(result, map[string]interface{}{
"address": addr, "address": addr,
"ip": client.IP, "ip": client.IP,
"port": client.Port, "port": client.Port,
"lastSeen": client.LastSeen, "lastSeen": client.LastSeen,
"isOnline": isOnline, "isOnline": isOnline,
"lastSeenFormatted": formatDuration(lastSeenDuration), "lastSeenFormatted": formatDuration(lastSeenDuration),
}) })
} }
return result return result
} }
// formatDuration 格式化持续时间为友好的字符串 // formatDuration 格式化持续时间为友好的字符串
func formatDuration(d time.Duration) string { func formatDuration(d time.Duration) string {
if d < time.Minute { if d < time.Minute {
return "刚刚" return "刚刚"
} else if d < time.Hour { } else if d < time.Hour {
return fmt.Sprintf("%d分钟前", int(d.Minutes())) return fmt.Sprintf("%d分钟前", int(d.Minutes()))
} else if d < 24*time.Hour { } else if d < 24*time.Hour {
return fmt.Sprintf("%d小时前", int(d.Hours())) return fmt.Sprintf("%d小时前", int(d.Hours()))
} else { } else {
return fmt.Sprintf("%d天前", int(d.Hours()/24)) return fmt.Sprintf("%d天前", int(d.Hours()/24))
} }
} }
// startClientCleanup 启动清理过期客户端的goroutine // startClientCleanup 启动清理过期客户端的goroutine
func startClientCleanup() { func startClientCleanup() {
go func() { go func() {
for { for {
time.Sleep(1 * time.Hour) // 每小时检查一次 time.Sleep(1 * time.Hour) // 每小时检查一次
clientsMutex.Lock() clientsMutex.Lock()
now := time.Now() now := time.Now()
for addr, client := range clients { for addr, client := range clients {
if now.Sub(client.LastSeen) > 24*time.Hour { if now.Sub(client.LastSeen) > 24*time.Hour {
delete(clients, addr) delete(clients, addr)
Logger.Printf("移除过期客户端: %s", addr) Logger.Printf("移除过期客户端: %s", addr)
} }
} }
clientsMutex.Unlock() clientsMutex.Unlock()
} }
}() }()
} }

File diff suppressed because it is too large Load Diff