低空智控平台 后端go
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

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)
}