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

129 lines
3.7 KiB

package service
import (
"errors"
"strconv"
"time"
"gorm.io/gorm"
"laic-backend/common"
"laic-backend/logger"
"laic-backend/model"
"laic-backend/tool"
)
type WorkflowService struct{}
var DefaultWorkflowService = &WorkflowService{}
// WorkflowStateIn 工作流状态上报载荷
type WorkflowStateIn struct {
CommandID string `json:"commandId"`
Type string `json:"type"`
TaskID string `json:"taskId"`
MissionID string `json:"missionId"`
State string `json:"state"`
Step string `json:"step"`
ResultCode string `json:"resultCode"`
}
// Upsert 持久化工作流状态(按 command_id 去重更新),并在终态时回写任务执行记录
func (s *WorkflowService) Upsert(dockID, requestID string, in *WorkflowStateIn) {
now := time.Now()
if requestID != "" {
var cmd model.DeviceCommandLog
if err := common.DB.Where("dock_id = ? AND request_id = ? AND status IN ?", dockID, requestID, []string{"sent", "acked"}).First(&cmd).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
logger.ERROR("按 requestId 查询工作流指令失败", err)
}
return
}
expected := strconv.FormatInt(cmd.ID, 10)
if in.CommandID != "" && in.CommandID != expected {
logger.WARN("工作流状态 commandId 与 requestId 不匹配", dockID, requestID)
return
}
in.CommandID = expected
}
if in.CommandID != "" {
var existing model.WorkflowState
err := common.DB.Where("dock_id = ? AND command_id = ?", dockID, in.CommandID).First(&existing).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
id, e := tool.NextID()
if e == nil {
common.DB.Create(&model.WorkflowState{
ID: id,
DockID: dockID,
CommandID: in.CommandID,
Type: in.Type,
TaskID: in.TaskID,
MissionID: in.MissionID,
State: in.State,
Step: in.Step,
ResultCode: in.ResultCode,
UpdatedAt: now,
})
}
} else if err == nil {
common.DB.Model(&existing).Updates(map[string]any{
"dock_id": dockID,
"type": in.Type,
"task_id": in.TaskID,
"mission_id": in.MissionID,
"state": in.State,
"step": in.Step,
"result_code": in.ResultCode,
"updated_at": now,
})
}
}
// 回写 task_execution,优先按 command_id 精确关联,兼容旧设备时只回退到最新未终态记录。
if (in.CommandID != "" || in.TaskID != "") && (in.State == "running" || isTerminalWorkflowState(in.State)) {
updates := map[string]any{"status": in.State}
if in.State == "running" {
updates["start_time"] = now
} else {
updates["end_time"] = now
updates["start_time"] = gorm.Expr("COALESCE(start_time, ?)", now)
}
query := common.DB.Model(&model.TaskExecution{}).
Where("dock_id = ? AND status NOT IN ('succeeded','failed','cancelled')", dockID)
if in.CommandID != "" {
query = query.Where("command_id = ?", in.CommandID)
} else {
query = query.Where("task_id = ?", in.TaskID)
var execution model.TaskExecution
if err := query.Order("created_at DESC, id DESC").First(&execution).Error; err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
logger.ERROR("查找任务执行记录失败", err)
}
query = nil
} else {
query = common.DB.Model(&execution)
}
}
if query != nil {
if err := query.Updates(updates).Error; err != nil {
logger.ERROR("回写任务执行状态失败", err)
}
}
}
// 终态回写轨迹(MySQL trajectory_json + TDengine task_trajectory)
if isTerminalWorkflowState(in.State) {
DefaultTrajectoryStore.Finalize(dockID, DefaultTelemetryStore.DroneSN(dockID))
}
}
func isTerminalWorkflowState(state string) bool {
switch state {
case "succeeded", "failed", "cancelled":
return true
default:
return false
}
}