You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
3.4 KiB
133 lines
3.4 KiB
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"laic-backend/common"
|
|
"laic-backend/logger"
|
|
"laic-backend/model"
|
|
"laic-backend/vo"
|
|
)
|
|
|
|
// trajectoryBuffer 单次执行的活动轨迹缓冲
|
|
type trajectoryBuffer struct {
|
|
ExecutionID int64
|
|
DockID string
|
|
DroneSN string
|
|
Points []vo.TrajectoryPoint
|
|
}
|
|
|
|
// TrajectoryStore 轨迹采集:维护每个机巢的活动执行,遥测点入缓冲,终态回写 MySQL + TDengine
|
|
type TrajectoryStore struct {
|
|
mu sync.Mutex
|
|
active map[string]*trajectoryBuffer // dockID -> 活动执行
|
|
}
|
|
|
|
var DefaultTrajectoryStore = &TrajectoryStore{active: make(map[string]*trajectoryBuffer)}
|
|
|
|
// Begin 建立活动执行关联(任务下发成功后调用)
|
|
func (s *TrajectoryStore) Begin(dockID string, executionID int64, droneSN string) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.active[dockID] = &trajectoryBuffer{
|
|
ExecutionID: executionID,
|
|
DockID: dockID,
|
|
DroneSN: droneSN,
|
|
Points: make([]vo.TrajectoryPoint, 0, 1024),
|
|
}
|
|
}
|
|
|
|
// Append 将遥测点追加到活动执行的轨迹缓冲
|
|
func (s *TrajectoryStore) Append(dockID string, p TelemetryPoint) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
buf, ok := s.active[dockID]
|
|
if !ok {
|
|
return
|
|
}
|
|
ts := p.Ts
|
|
if ts <= 0 {
|
|
ts = time.Now().UnixMilli()
|
|
}
|
|
buf.Points = append(buf.Points, vo.TrajectoryPoint{
|
|
Ts: ts,
|
|
Longitude: p.Longitude,
|
|
Latitude: p.Latitude,
|
|
Altitude: p.Altitude,
|
|
GroundSpeed: p.GroundSpeed,
|
|
Yaw: p.Yaw,
|
|
BatteryPct: p.BatteryPct,
|
|
})
|
|
}
|
|
|
|
// Finalize 终态回写:轨迹点写 MySQL trajectory_json + TDengine task_trajectory,并清除活动关联
|
|
func (s *TrajectoryStore) Finalize(dockID string, droneSN string) {
|
|
s.mu.Lock()
|
|
buf, ok := s.active[dockID]
|
|
if ok {
|
|
delete(s.active, dockID)
|
|
}
|
|
s.mu.Unlock()
|
|
if !ok || buf == nil {
|
|
return
|
|
}
|
|
if droneSN != "" {
|
|
buf.DroneSN = droneSN
|
|
}
|
|
|
|
pointsJSON, err := json.Marshal(buf.Points)
|
|
if err != nil {
|
|
pointsJSON = []byte("[]")
|
|
}
|
|
updates := map[string]any{"trajectory_json": string(pointsJSON)}
|
|
if buf.DroneSN != "" {
|
|
updates["drone_sn"] = buf.DroneSN
|
|
}
|
|
if err := common.DB.Model(&model.TaskExecution{}).
|
|
Where("id = ?", buf.ExecutionID).
|
|
Updates(updates).Error; err != nil {
|
|
logger.ERROR("回写执行轨迹 JSON 失败", err)
|
|
}
|
|
|
|
s.writeTDengine(buf)
|
|
}
|
|
|
|
// writeTDengine 轨迹点批量写入 task_trajectory 超级表(子表按 execution_id 隔离)
|
|
func (s *TrajectoryStore) writeTDengine(buf *trajectoryBuffer) {
|
|
if common.TD == nil || len(buf.Points) == 0 {
|
|
return
|
|
}
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
logger.ERROR("TDengine 轨迹写入 panic(已恢复)", fmt.Errorf("%v", r))
|
|
}
|
|
}()
|
|
|
|
var b strings.Builder
|
|
b.WriteString("INSERT INTO ")
|
|
b.WriteString(trajectoryTable(buf.ExecutionID))
|
|
b.WriteString(" USING task_trajectory TAGS (")
|
|
fmt.Fprintf(&b, "%d, '%s', '%s') VALUES ",
|
|
buf.ExecutionID, escapeTD(buf.DockID), escapeTD(buf.DroneSN))
|
|
for i, p := range buf.Points {
|
|
if i > 0 {
|
|
b.WriteString(", ")
|
|
}
|
|
fmt.Fprintf(&b, "(%d, %s, %s, %s, %s, %s, %d)",
|
|
p.Ts, ftoa(p.Longitude), ftoa(p.Latitude), ftoa(p.Altitude),
|
|
ftoa(p.GroundSpeed), ftoa(p.Yaw), p.BatteryPct)
|
|
}
|
|
if _, err := common.TD.Exec(b.String()); err != nil {
|
|
logger.ERROR("TDengine 批量写入轨迹失败", err)
|
|
}
|
|
}
|
|
|
|
// trajectoryTable 按执行 ID 生成子表名
|
|
func trajectoryTable(execID int64) string {
|
|
return "tr_" + strconv.FormatInt(execID, 10)
|
|
}
|
|
|